diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..dcc8024
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,61 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+.vscode/
+.idea/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.pub-cache/
+.pub/
+/build/
+/test
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
+
+# Firebase Related
+**/google-services.json
+**/GoogleService-Info.plist
+**/firebase_app_id_file.json
+**/firebase_options.dart
+.firebaserc
+.metadata
+.firebase/hosting.LmZpcmViYXNlXHNwb29uc2hhcmUtbWVhbHNcaG9zdGluZw.cache
+.firebase/spoonshare-meals/hosting/.last_build_id
+.firebase/
+ios/Flutter/Debug.xcconfig
+ios/Flutter/Release.xcconfig
+ios/Runner.xcodeproj/project.pbxproj
+ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+.env
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..89e977c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Shubham Pitekar
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..44a7158
--- /dev/null
+++ b/README.md
@@ -0,0 +1,143 @@
+
+# SpoonShare 🥣
+
+**Problem Statement**: Inadequate surplus food distribution generates hunger, necessitating a comprehensive solution. Our project addresses this challenge through an innovative platform, connecting donors with recipients to bridge the gap in food distribution.
+
+A **Google Solution Challenge Project'24** Organised By Google Developer Student Clubs Project by **Team Innovision Squad From Deogiri Institute of Engineering And Management Studies Chh. Sambhajinagar.**
+
+##
+
+## Live Preview
+
+Here you can view the deployed version
+[SpoonShare](https://spoonshare-meals.web.app/)
+
+
+# SpoonShare Project
+
+## Intro To SpoonShare Video
+
+[![Intro To SpoonShare](https://github.com/shuence/SpoonShare/assets/65482186/ff3a926f-f796-4609-8502-6b5948efcbd5)
+](https://www.youtube.com/watch?v=IKbxF7SYE3Q&ab_channel=SanikaChavan)
+
+
+Short but detailed introduction to SpoonShare. Click on the image above to watch the video.
+
+
+## Setup
+
+To Setup this project run
+
+```bash
+git clone https://github.com/shuence/SpoonShare
+cd SpoonShare
+flutter pub get
+flutter run
+```
+
+## Resources
+
+- [Flutter Docs](https://docs.flutter.dev/)
+- [Figma](https://help.figma.com/hc/en-us)
+- [Firebase Docs](https://firebase.google.com/docs)
+
+# Screenshots
+
+
+
+# SpoonShare Features
+
+- **Surplus Food Map:** Visualize and navigate locations with surplus food, ensuring efficient distribution.
+- **User-Friendly Interface:** Intuitive design for seamless interaction for both donors and recipients.
+- **Real-Time Updates:** Keep users informed with instant notifications on surplus food availability and distribution.
+- **Multi-Language Support:** Ensure inclusivity by providing support for multiple languages.
+- **Integration with Social Media:** Facilitate broader outreach and engagement through social media integration.
+- **Gamification Elements:** Incentivize frequent donors with rewards and gamified features.
+- **Educational Resources:** Offer information on sustainable practices and the impact of food wastage.
+- **Volunteer Matching:** Connect volunteers with surplus food distribution opportunities based on their preferences and availability.
+- **Donor Recognition:** Acknowledge and appreciate donors for their contributions through a recognition system.
+- **Quality and Safety Standards Verification:** Establish and enforce guidelines to ensure the quality and safety of donated food.
+- **Feedback and Ratings System:** Promote transparency and accountability through user feedback and ratings.
+- **In-App Challenges and Campaigns:** Engage users with interactive challenges and campaigns to encourage participation.
+- **Collaboration with Local Governments and NGO'S:** Foster partnerships with local authorities to streamline operations and adhere to regulations.
+## Tech Stack
+**Technologies involved/used:**
+- **Flutter:** Google's UI toolkit for cross-platform app development.
+- **Firebase:** Google's platform for authentication, database, and cloud services.
+- **Google Maps API:** Integrates dynamic maps and location-based services.
+- **NLP tools:** Enables text analysis and language understanding.
+- **Google Cloud:** Offers scalable cloud services and machine learning.
+- **Android Studio:** Official IDE for Android development.
+- **Web (HTML, CSS, JS):** Standard web technologies for UI.
+- **Google Maps:** Web mapping service for interactive maps.
+- **Google API:** Collection of APIs for diverse services.
+- **Google Analytics:** Tracks and reports website/app traffic.
+- **Google Sign-In:** Authentication using Google credentials.
+- **Google Speech API:** Integrates speech recognition capabilities.
+# SpoonShare Project Implementation Overview
+
+### Technology Stack
+- Flutter: Cross-platform app development.
+- Firebase: Real-time updates, user authentication, and data storage.
+- Google Maps API: Efficient navigation.
+
+### User Interface (UI) Design
+- Figma: Collaborative UI/UX design.
+- User-friendly interface with clear "Donate Food" and "Find Food" buttons.
+
+### Chatbot Integration
+- Dialogflow: Interactive chatbot functionality.
+- Friendly and supportive chatbot tone for enhanced engagement.
+
+### Gamification Elements
+- Flutter: Implementation of gamification features.
+- Rewards for frequent donors to encourage sustained engagement.
+
+### Multi-Language Support
+- Flutter's localization tools for supporting multiple languages.
+
+### Educational Resources
+- Collaboration with NGOs to provide educational content on food waste.
+
+### Volunteer Matching
+- Feature to connect willing volunteers with NGOs and events.
+
+### Quality and Safety Standards Verification
+- Establishment of guidelines for donor verification.
+
+### Feedback and Ratings System
+- System to maintain transparency and encourage user participation.
+
+### In-App Challenges and Campaigns
+- Engaging challenges and campaigns for sustained user interest.
+
+### Collaboration with Local Governments
+- Partnerships with local governments for legal compliance.
+
+### Marketing and Awareness
+- Utilization of social media platforms for promotional campaigns.
+- Collaboration with influencers and organizations for a wider reach.
+
+### Post-Launch Optimization
+- Regular analysis of user data for improvements and enhancements.
+- Community feedback encouraged for continuous improvement.
+
+### Community Building and Partnerships
+- Robust community engagement strategy for user interaction.
+- Partnerships with NGOs, local businesses, and institutions for expanded impact.
+
+# Hi, We are InnovisionSquad! 👋
+
+
+## 🚀 About us
+
+We are a team from Deogiri Institute of Engineering And Management Studies Chh. Sambhajinagar and Core Team Members of [GDSC DIEMS](https://gdsc.community.dev/deogiri-institute-of-engineering-and-management-studies-aurangabad/)
+
+- Sanika Chavan - [Sanika](https://linkedin.com/in/sanika-chavan-52457b236/)
+- Krishna Dnyaneshwar Aute - [Krishna](https://www.linkedin.com/in/krishna-aute-195b2b135/)
+- Shubham Vishnu Pitekar - [Shuence](https://shuence.com)
+- Mohammed Rehan - [Rehan](https://www.linkedin.com/in/mdrehan15/)
+
+## Happy coding 💯
+
+Made with love from [InnovsionSquad]() ❤️
diff --git a/analysis_options.yaml b/analysis_options.yaml
new file mode 100644
index 0000000..0d29021
--- /dev/null
+++ b/analysis_options.yaml
@@ -0,0 +1,28 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+ # The lint rules applied to this project can be customized in the
+ # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+ # included above or to enable additional rules. A list of all available lints
+ # and their documentation is published at https://dart.dev/lints.
+ #
+ # Instead of disabling a lint rule for the entire project in the
+ # section below, it can also be suppressed for a single line of code
+ # or a specific dart file by using the `// ignore: name_of_lint` and
+ # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+ # producing the lint.
+ rules:
+ # avoid_print: false # Uncomment to disable the `avoid_print` rule
+ # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/android/.gitignore b/android/.gitignore
new file mode 100644
index 0000000..6f56801
--- /dev/null
+++ b/android/.gitignore
@@ -0,0 +1,13 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/android/app/build.gradle b/android/app/build.gradle
new file mode 100644
index 0000000..faa564e
--- /dev/null
+++ b/android/app/build.gradle
@@ -0,0 +1,81 @@
+plugins {
+ id "com.android.application"
+ id "kotlin-android"
+ id "dev.flutter.flutter-gradle-plugin"
+ id 'com.google.gms.google-services'
+ id 'com.google.firebase.crashlytics'
+}
+
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+ localPropertiesFile.withReader('UTF-8') { reader ->
+ localProperties.load(reader)
+ }
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+ flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+ flutterVersionName = '1.0'
+}
+
+android {
+ namespace "com.example.spoonsharemeals"
+ compileSdkVersion flutter.compileSdkVersion
+ ndkVersion flutter.ndkVersion
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ sourceSets {
+ main.java.srcDirs += 'src/main/kotlin'
+ }
+
+ defaultConfig {
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+ applicationId "com.example.spoonsharemeals.spoonsharemeals"
+ // You can update the following values to match your application needs.
+ // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
+ minSdkVersion flutter.minSdkVersion
+ targetSdkVersion flutter.targetSdkVersion
+ versionCode flutterVersionCode.toInteger()
+ versionName flutterVersionName
+ multiDexEnabled true
+ }
+
+ buildTypes {
+ release {
+ // TODO: Add your own signing config for the release build.
+ // Signing with the debug keys for now, so `flutter run --release` works.
+ signingConfig signingConfigs.debug
+ }
+ }
+}
+
+flutter {
+ source '../..'
+}
+
+dependencies {
+ implementation 'com.android.support:multidex:1.0.3'
+ implementation platform('com.google.firebase:firebase-bom:32.7.1')
+ implementation("com.google.firebase:firebase-crashlytics")
+ implementation("com.google.firebase:firebase-perf")
+ implementation ('com.google.firebase:firebase-analytics')
+ implementation ('com.google.firebase:firebase-auth')
+ implementation ('com.google.firebase:firebase-firestore')
+ implementation ('com.google.firebase:firebase-storage')
+ implementation ('com.google.firebase:firebase-database')
+ implementation ('com.google.firebase:firebase-messaging')
+}
diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..399f698
--- /dev/null
+++ b/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..7765428
--- /dev/null
+++ b/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/kotlin/com/example/spoonsharemeals/MainActivity.kt b/android/app/src/main/kotlin/com/example/spoonsharemeals/MainActivity.kt
new file mode 100644
index 0000000..e257a51
--- /dev/null
+++ b/android/app/src/main/kotlin/com/example/spoonsharemeals/MainActivity.kt
@@ -0,0 +1,6 @@
+package com.example.spoonsharemeals
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
diff --git a/android/app/src/main/kotlin/com/example/spoonsharemeals/spoonsharemeals/MainActivity.kt b/android/app/src/main/kotlin/com/example/spoonsharemeals/spoonsharemeals/MainActivity.kt
new file mode 100644
index 0000000..458a24a
--- /dev/null
+++ b/android/app/src/main/kotlin/com/example/spoonsharemeals/spoonsharemeals/MainActivity.kt
@@ -0,0 +1,6 @@
+package com.example.spoonsharemeals.spoonsharemeals
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/launcher_icon.png b/android/app/src/main/res/mipmap-hdpi/launcher_icon.png
new file mode 100644
index 0000000..289bd86
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/launcher_icon.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/launcher_icon.png b/android/app/src/main/res/mipmap-mdpi/launcher_icon.png
new file mode 100644
index 0000000..1d4a454
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/launcher_icon.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png
new file mode 100644
index 0000000..98f087b
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png
new file mode 100644
index 0000000..e304c5a
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png b/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png
new file mode 100644
index 0000000..d443fae
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png differ
diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..06952be
--- /dev/null
+++ b/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..cb1ef88
--- /dev/null
+++ b/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..399f698
--- /dev/null
+++ b/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/android/build.gradle b/android/build.gradle
new file mode 100644
index 0000000..6241e7e
--- /dev/null
+++ b/android/build.gradle
@@ -0,0 +1,31 @@
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath "com.android.tools.build:gradle:7.3.0"
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22"
+ classpath 'com.google.gms:google-services:4.4.0'
+ classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9'
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+ project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+ project.evaluationDependsOn(':app')
+}
+
+tasks.register("clean", Delete) {
+ delete rootProject.buildDir
+}
diff --git a/android/gradle.properties b/android/gradle.properties
new file mode 100644
index 0000000..598d13f
--- /dev/null
+++ b/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx4G
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..3c472b9
--- /dev/null
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
diff --git a/android/settings.gradle b/android/settings.gradle
new file mode 100644
index 0000000..7cd7128
--- /dev/null
+++ b/android/settings.gradle
@@ -0,0 +1,29 @@
+pluginManagement {
+ def flutterSdkPath = {
+ def properties = new Properties()
+ file("local.properties").withInputStream { properties.load(it) }
+ def flutterSdkPath = properties.getProperty("flutter.sdk")
+ assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+ return flutterSdkPath
+ }
+ settings.ext.flutterSdkPath = flutterSdkPath()
+
+ includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
+
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+
+ plugins {
+ id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
+ }
+}
+
+plugins {
+ id "dev.flutter.flutter-plugin-loader" version "1.0.0"
+ id "com.android.application" version "7.3.0" apply false
+}
+
+include ":app"
diff --git a/assets/images/google.png b/assets/images/google.png
new file mode 100644
index 0000000..221d6cb
Binary files /dev/null and b/assets/images/google.png differ
diff --git a/assets/images/mail.png b/assets/images/mail.png
new file mode 100644
index 0000000..ed1b52a
Binary files /dev/null and b/assets/images/mail.png differ
diff --git a/assets/images/onboarding.png b/assets/images/onboarding.png
new file mode 100644
index 0000000..3e9de56
Binary files /dev/null and b/assets/images/onboarding.png differ
diff --git a/assets/images/playstore.png b/assets/images/playstore.png
new file mode 100644
index 0000000..6b5fc79
Binary files /dev/null and b/assets/images/playstore.png differ
diff --git a/assets/images/spoonshare_launcher.png b/assets/images/spoonshare_launcher.png
new file mode 100644
index 0000000..a98f3d5
Binary files /dev/null and b/assets/images/spoonshare_launcher.png differ
diff --git a/assets/images/thankyou.jpg b/assets/images/thankyou.jpg
new file mode 100644
index 0000000..f424c2c
Binary files /dev/null and b/assets/images/thankyou.jpg differ
diff --git a/firebase.json b/firebase.json
new file mode 100644
index 0000000..e912a28
--- /dev/null
+++ b/firebase.json
@@ -0,0 +1,13 @@
+{
+ "hosting": {
+ "source": ".",
+ "ignore": [
+ "firebase.json",
+ "**/.*",
+ "**/node_modules/**"
+ ],
+ "frameworksBackend": {
+ "region": "us-central1"
+ }
+ }
+}
diff --git a/ios/.gitignore b/ios/.gitignore
new file mode 100644
index 0000000..7a7f987
--- /dev/null
+++ b/ios/.gitignore
@@ -0,0 +1,34 @@
+**/dgph
+*.mode1v3
+*.mode2v3
+*.moved-aside
+*.pbxuser
+*.perspectivev3
+**/*sync/
+.sconsign.dblite
+.tags*
+**/.vagrant/
+**/DerivedData/
+Icon?
+**/Pods/
+**/.symlinks/
+profile
+xcuserdata
+**/.generated/
+Flutter/App.framework
+Flutter/Flutter.framework
+Flutter/Flutter.podspec
+Flutter/Generated.xcconfig
+Flutter/ephemeral/
+Flutter/app.flx
+Flutter/app.zip
+Flutter/flutter_assets/
+Flutter/flutter_export_environment.sh
+ServiceDefinitions.json
+Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
+!default.mode1v3
+!default.mode2v3
+!default.pbxuser
+!default.perspectivev3
diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist
new file mode 100644
index 0000000..7c56964
--- /dev/null
+++ b/ios/Flutter/AppFrameworkInfo.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ App
+ CFBundleIdentifier
+ io.flutter.flutter.app
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ App
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1.0
+ MinimumOSVersion
+ 12.0
+
+
diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..1d526a1
--- /dev/null
+++ b/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift
new file mode 100644
index 0000000..70693e4
--- /dev/null
+++ b/ios/Runner/AppDelegate.swift
@@ -0,0 +1,13 @@
+import UIKit
+import Flutter
+
+@UIApplicationMain
+@objc class AppDelegate: FlutterAppDelegate {
+ override func application(
+ _ application: UIApplication,
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
+ ) -> Bool {
+ GeneratedPluginRegistrant.register(with: self)
+ return super.application(application, didFinishLaunchingWithOptions: launchOptions)
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..d36b1fa
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,122 @@
+{
+ "images" : [
+ {
+ "size" : "20x20",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-20x20@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-20x20@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-40x40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-40x40@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-60x60@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-60x60@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-20x20@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-20x20@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-29x29@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-29x29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-40x40@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-40x40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-76x76@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-76x76@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "83.5x83.5",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-83.5x83.5@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "1024x1024",
+ "idiom" : "ios-marketing",
+ "filename" : "Icon-App-1024x1024@1x.png",
+ "scale" : "1x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
new file mode 100644
index 0000000..3d1b0cc
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
new file mode 100644
index 0000000..4c39fe6
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
new file mode 100644
index 0000000..8bc0b7e
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
new file mode 100644
index 0000000..d5378c2
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
new file mode 100644
index 0000000..90f98b6
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
new file mode 100644
index 0000000..ee917f6
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
new file mode 100644
index 0000000..fc9f31f
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
new file mode 100644
index 0000000..8bc0b7e
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
new file mode 100644
index 0000000..7e97deb
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
new file mode 100644
index 0000000..5c21eef
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png
new file mode 100644
index 0000000..dc9bb0b
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png
new file mode 100644
index 0000000..4e3b471
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png
new file mode 100644
index 0000000..8d7c624
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png
new file mode 100644
index 0000000..b3f05ab
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
new file mode 100644
index 0000000..5c21eef
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
new file mode 100644
index 0000000..8c6f124
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png
new file mode 100644
index 0000000..289bd86
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png
new file mode 100644
index 0000000..e304c5a
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
new file mode 100644
index 0000000..aae4184
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
new file mode 100644
index 0000000..fbad615
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
new file mode 100644
index 0000000..780a0c2
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
new file mode 100644
index 0000000..0bedcf2
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@3x.png",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
new file mode 100644
index 0000000..9da19ea
Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
new file mode 100644
index 0000000..9da19ea
Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
new file mode 100644
index 0000000..9da19ea
Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
new file mode 100644
index 0000000..89c2725
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
@@ -0,0 +1,5 @@
+# Launch Screen Assets
+
+You can customize the launch screen with your own desired assets by replacing the image files in this directory.
+
+You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..f2e259c
--- /dev/null
+++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..f3c2851
--- /dev/null
+++ b/ios/Runner/Base.lproj/Main.storyboard
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
new file mode 100644
index 0000000..6f94d9e
--- /dev/null
+++ b/ios/Runner/Info.plist
@@ -0,0 +1,49 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ Spoonsharemeals
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ spoonsharemeals
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ CADisableMinimumFrameDurationOnPhone
+
+ UIApplicationSupportsIndirectInputEvents
+
+
+
diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h
new file mode 100644
index 0000000..308a2a5
--- /dev/null
+++ b/ios/Runner/Runner-Bridging-Header.h
@@ -0,0 +1 @@
+#import "GeneratedPluginRegistrant.h"
diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift
new file mode 100644
index 0000000..86a7c3b
--- /dev/null
+++ b/ios/RunnerTests/RunnerTests.swift
@@ -0,0 +1,12 @@
+import Flutter
+import UIKit
+import XCTest
+
+class RunnerTests: XCTestCase {
+
+ func testExample() {
+ // If you add code to the Runner application, consider adding tests here.
+ // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
+ }
+
+}
diff --git a/lib/controllers/auth/signin_controller.dart b/lib/controllers/auth/signin_controller.dart
new file mode 100644
index 0000000..6826c8b
--- /dev/null
+++ b/lib/controllers/auth/signin_controller.dart
@@ -0,0 +1,190 @@
+// ignore_for_file: use_build_context_synchronously
+
+import 'package:flutter/material.dart';
+import 'package:spoonshare/screens/home/home.dart';
+import 'package:spoonshare/widgets/snackbar.dart';
+import 'package:firebase_auth/firebase_auth.dart';
+import 'package:cloud_firestore/cloud_firestore.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+import 'package:google_sign_in/google_sign_in.dart';
+
+class SignInController {
+ final FirebaseAuth _auth = FirebaseAuth.instance;
+ final FirebaseFirestore _firestore = FirebaseFirestore.instance;
+ final GoogleSignIn _googleSignIn = GoogleSignIn();
+
+ Future signIn({
+ required String email,
+ required String password,
+ BuildContext? context,
+ }) async {
+ if (!_isValidInputEmail(email, context) ||
+ !_isValidInputPassword(password, context)) {
+ return;
+ }
+
+ try {
+ // Sign in with email and password
+ UserCredential userCredential = await _auth.signInWithEmailAndPassword(
+ email: email, password: password);
+
+ // Check if email is verified
+
+ /* if (userCredential.user!.emailVerified) {
+ _showSuccessSnackbar(context, 'Signup successful');
+ // Navigate to the desired screen (e.g., Onboarding)
+
+ }
+ */
+//else {
+
+ /* If email is not verified, provide an option to resend the verification email
+ _showEmailVerificationDialog(context);
+ }*/
+ // Load user details and save locally
+ await _loadAndSaveUserLocally(userCredential.user!);
+
+ // Show success message
+ _showSuccessSnackbar(context, 'Signin successful');
+ } catch (e) {
+ // Show error message
+ _showErrorSnackbar(context, "Error: $e");
+ }
+ }
+
+ Future signInWithGoogle(BuildContext? context) async {
+ try {
+ // Trigger Google Sign In
+ final GoogleSignInAccount? googleSignInAccount =
+ await _googleSignIn.signIn();
+
+ if (googleSignInAccount == null) {
+ // Google sign-in canceled
+ return;
+ }
+
+ final GoogleSignInAuthentication googleSignInAuthentication =
+ await googleSignInAccount.authentication;
+
+ // Sign in to Firebase with Google credentials
+ final OAuthCredential googleAuthCredential =
+ GoogleAuthProvider.credential(
+ accessToken: googleSignInAuthentication.accessToken,
+ idToken: googleSignInAuthentication.idToken,
+ );
+
+ UserCredential userCredential =
+ await _auth.signInWithCredential(googleAuthCredential);
+
+ // Load and save user details
+ await _loadAndSaveUserLocally(userCredential.user!);
+
+ // Show success message
+ _showSuccessSnackbar(context, 'Google Signin successful');
+
+ // You can navigate to the desired screen after successful signup
+ Navigator.pushReplacement(
+ context!,
+ MaterialPageRoute(
+ builder: (context) => const HomeScreen(),
+ ),
+ );
+ } catch (e) {
+ // Show error message
+ _showErrorSnackbar(context, "Error: $e");
+ }
+ }
+
+Future _loadAndSaveUserLocally(User user) async {
+ try {
+ DocumentSnapshot userDoc =
+ await _firestore.collection('users').doc(user.uid).get();
+
+ if (userDoc.exists) {
+ String fullName = userDoc['fullName'];
+ String contactNumber = userDoc['contactNumber'];
+ String profileImageUrl = userDoc['profileImageUrl'];
+ String role = userDoc['role'];
+ String organisation = userDoc['organisation'];
+
+ await _saveUserLocally(fullName, user.email!, contactNumber, role, profileImageUrl, organisation);
+ } else {
+ throw Exception("User document does not exist");
+ }
+ } catch (e) {
+ print("Error loading user profile: $e");
+ }
+}
+
+ Future _saveUserLocally(
+ String fullName, String email, String contactNumber, String role, String ProfileImageUrl, String organisation) async {
+ SharedPreferences prefs = await SharedPreferences.getInstance();
+ prefs.setString('fullName', fullName);
+ prefs.setString('email', email);
+ prefs.setString('contactNumber', contactNumber);
+ prefs.setString('profileImageUrl', ProfileImageUrl);
+ prefs.setString("role", role);
+ prefs.setString("organisation", organisation);
+ }
+
+ void _showEmailVerificationDialog(BuildContext? context) {
+ // Show a dialog with an option to resend the verification email
+ showDialog(
+ context: context!,
+ builder: (BuildContext context) {
+ return AlertDialog(
+ title: const Text('Email Verification'),
+ content: const Text(
+ 'Please verify your email by clicking the verification link sent to your email address. If you haven\'t received the email, you can resend it.',
+ ),
+ actions: [
+ TextButton(
+ onPressed: () async {
+ // Resend verification email
+ await _auth.currentUser!.sendEmailVerification();
+ Navigator.pop(context);
+ _showSuccessSnackbar(context, 'Verification email resent');
+ },
+ child: const Text('Resend Email'),
+ ),
+ TextButton(
+ onPressed: () {
+ Navigator.pop(context);
+ },
+ child: const Text('OK'),
+ ),
+ ],
+ );
+ },
+ );
+ }
+
+ bool _isValidInputEmail(String email, BuildContext? context) {
+ if (!RegExp(r'^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$')
+ .hasMatch(email)) {
+ _showErrorSnackbar(context, 'Invalid Email');
+ return false;
+ }
+ return true;
+ }
+
+ bool _isValidInputPassword(String password, BuildContext? context) {
+ if (password.isEmpty || password.length < 6) {
+ _showErrorSnackbar(context, 'Invalid Password');
+ return false;
+ }
+ return true;
+ }
+
+ void _showErrorSnackbar(BuildContext? context, String message) {
+ if (context != null) {
+ showErrorSnackbar(context, message);
+ }
+ }
+
+ void _showSuccessSnackbar(BuildContext? context, String message) {
+ if (context != null) {
+ showSuccessSnackbar(context, message);
+ }
+ }
+}
diff --git a/lib/controllers/auth/signup_controller.dart b/lib/controllers/auth/signup_controller.dart
new file mode 100644
index 0000000..1a656f5
--- /dev/null
+++ b/lib/controllers/auth/signup_controller.dart
@@ -0,0 +1,244 @@
+// ignore_for_file: use_build_context_synchronously
+
+import 'package:flutter/material.dart';
+import 'package:spoonshare/screens/home/home.dart';
+import 'package:spoonshare/widgets/snackbar.dart';
+import 'package:firebase_auth/firebase_auth.dart';
+import 'package:cloud_firestore/cloud_firestore.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+import 'package:google_sign_in/google_sign_in.dart';
+
+class SignUpController {
+ final FirebaseAuth _auth = FirebaseAuth.instance;
+ final FirebaseFirestore _firestore = FirebaseFirestore.instance;
+ final GoogleSignIn _googleSignIn = GoogleSignIn();
+
+ Future signUp({
+ required String fullName,
+ required String email,
+ required String contactNumber,
+ required String password,
+ required String confirmPassword,
+ String? profileImageUrl,
+ String? role,
+ String? organisation,
+ BuildContext? context,
+ }) async {
+ if (!_isValidInput(fullName, 'Full Name', context) ||
+ !_isValidInputEmail(email, context) ||
+ !_isValidInputContactNumber(contactNumber, context) ||
+ !_isValidInputPassword(password, context) ||
+ !_arePasswordsMatching(password, confirmPassword, context)) {
+ return;
+ }
+
+ try {
+ // Create user with email and password
+ UserCredential userCredential =
+ await _auth.createUserWithEmailAndPassword(
+ email: email,
+ password: password,
+ );
+
+ // Save user details to Firestore
+ if (userCredential.user != null) {
+ // Save user details to Firestore
+ await _firestore.collection('users').doc(userCredential.user!.uid).set({
+ 'fullName': fullName,
+ 'email': email,
+ 'contactNumber': contactNumber,
+ 'profileImageUrl': profileImageUrl ??
+ "https://github.com/shuence/AdventureSquad/blob/main/user.png",
+ 'role': role ?? "Individual",
+ 'organisation': organisation ?? "",
+ });
+
+ // Save user details locally using SharedPreferences
+ await _saveUserLocally(fullName, email, contactNumber,
+ profileImageUrl ?? "", role ?? "Individual", organisation ?? "");
+
+ // Show success message
+ _showSuccessSnackbar(context, 'Signup successful');
+
+ // Send email verification
+ await userCredential.user!.sendEmailVerification();
+ } else {
+ // Handle the case where userCredential.user is null
+ _showErrorSnackbar(context, 'Error: User data is null');
+ }
+ } catch (e) {
+ // Show error message
+ _showErrorSnackbar(context, "Error: $e");
+ }
+ }
+
+ Future signUpWithGoogle(BuildContext? context) async {
+ try {
+ // Trigger Google Sign In
+ final GoogleSignInAccount? googleSignInAccount =
+ await _googleSignIn.signIn();
+
+ if (googleSignInAccount == null) {
+ // Google sign-in canceled
+ return;
+ }
+
+ final GoogleSignInAuthentication googleSignInAuthentication =
+ await googleSignInAccount.authentication;
+
+ // Sign in to Firebase with Google credentials
+ final OAuthCredential googleAuthCredential =
+ GoogleAuthProvider.credential(
+ accessToken: googleSignInAuthentication.accessToken,
+ idToken: googleSignInAuthentication.idToken,
+ );
+
+ UserCredential userCredential =
+ await _auth.signInWithCredential(googleAuthCredential);
+
+ // Fetch additional details from Google Sign-In
+ String fullName = userCredential.user?.displayName ?? '';
+ String email = userCredential.user?.email ?? '';
+ String contactNumber = '';
+ String profileImageUrl = userCredential.user?.photoURL ?? '';
+ String? role;
+ String? organisation;
+
+ // Save user details to Firestore (modify as needed)
+ await _firestore.collection('users').doc(userCredential.user?.uid).set({
+ 'fullName': fullName,
+ 'email': email,
+ 'contactNumber': contactNumber,
+ 'profileImageUrl': profileImageUrl,
+ 'role': role ?? "Individual",
+ 'organisation': organisation ?? "",
+ });
+ await _saveUserLocally(fullName, email, contactNumber, profileImageUrl,
+ role ?? "Individual", organisation ?? "");
+
+ // Show success message
+ _showSuccessSnackbar(context, 'Google Signup successful');
+
+ Navigator.pushReplacement(
+ context!,
+ MaterialPageRoute(
+ builder: (context) => const HomeScreen(),
+ ),
+ );
+ } catch (e) {
+ // Show error message
+ _showErrorSnackbar(context, "Error: $e");
+ }
+ }
+
+ Future _saveUserLocally(
+ String fullName,
+ String email,
+ String contactNumber,
+ String profileImageUrl,
+ String role,
+ String organisation) async {
+ SharedPreferences prefs = await SharedPreferences.getInstance();
+ prefs.setString('fullName', fullName);
+ prefs.setString('email', email);
+ prefs.setString('contactNumber', contactNumber);
+ prefs.setString('profileImageUrl', profileImageUrl);
+ prefs.setString('role', role);
+ prefs.setString('organisation', organisation);
+ }
+
+ void _showEmailVerificationDialog(BuildContext? context) {
+ // Show a dialog with an option to resend the verification email
+ showDialog(
+ context: context!,
+ builder: (BuildContext context) {
+ return AlertDialog(
+ title: const Text('Email Verification'),
+ content: const Text(
+ 'Please verify your email by clicking the verification link sent to your email address. If you haven\'t received the email, you can resend it.',
+ ),
+ actions: [
+ TextButton(
+ onPressed: () async {
+ // Resend verification email
+ await _auth.currentUser!.sendEmailVerification();
+ Navigator.pop(context);
+ _showSuccessSnackbar(context, 'Verification email resent');
+ },
+ child: const Text('Resend Email'),
+ ),
+ TextButton(
+ onPressed: () {
+ Navigator.pop(context);
+ },
+ child: const Text('OK'),
+ ),
+ ],
+ );
+ },
+ );
+ }
+
+ bool _isValidInput(String value, String field, BuildContext? context) {
+ if (value.isEmpty) {
+ _showErrorSnackbar(context, 'Invalid $field');
+ return false;
+ }
+ return true;
+ }
+
+ bool _isValidInputEmail(String email, BuildContext? context) {
+ if (!RegExp(r'^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$')
+ .hasMatch(email)) {
+ _showErrorSnackbar(context, 'Invalid Email');
+ return false;
+ }
+ return true;
+ }
+
+ bool _isValidInputContactNumber(String contactNumber, BuildContext? context) {
+ if (contactNumber.isEmpty ||
+ contactNumber.length != 10 ||
+ !isNumeric(contactNumber)) {
+ _showErrorSnackbar(context, 'Invalid Contact Number');
+ return false;
+ }
+ return true;
+ }
+
+ bool isNumeric(String? str) {
+ if (str == null) {
+ return false;
+ }
+ return int.tryParse(str) != null;
+ }
+
+ bool _isValidInputPassword(String password, BuildContext? context) {
+ if (password.isEmpty || password.length < 6) {
+ _showErrorSnackbar(context, 'Invalid Password');
+ return false;
+ }
+ return true;
+ }
+
+ bool _arePasswordsMatching(
+ String password, String confirmPassword, BuildContext? context) {
+ if (password != confirmPassword) {
+ _showErrorSnackbar(context, 'Passwords do not match');
+ return false;
+ }
+ return true;
+ }
+
+ void _showErrorSnackbar(BuildContext? context, String message) {
+ if (context != null) {
+ showErrorSnackbar(context, message);
+ }
+ }
+
+ void _showSuccessSnackbar(BuildContext? context, String message) {
+ if (context != null) {
+ showSuccessSnackbar(context, message);
+ }
+ }
+}
diff --git a/lib/main.dart b/lib/main.dart
new file mode 100644
index 0000000..5f73865
--- /dev/null
+++ b/lib/main.dart
@@ -0,0 +1,32 @@
+import 'package:flutter/material.dart';
+import 'package:firebase_core/firebase_core.dart';
+import 'package:spoonshare/firebase_options.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+import 'package:spoonshare/splash_screen.dart';
+
+void main() async {
+ WidgetsFlutterBinding.ensureInitialized();
+ await Firebase.initializeApp(
+ options: DefaultFirebaseOptions.currentPlatform,
+ );
+ await SharedPreferences.getInstance();
+
+ runApp(const MyApp());
+}
+
+class MyApp extends StatelessWidget {
+ const MyApp({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ title: 'Spoon Share',
+ theme: ThemeData(
+ colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
+ useMaterial3: true,
+ ),
+ home: const SplashScreen(),
+ debugShowCheckedModeBanner: false,
+ );
+ }
+}
diff --git a/lib/models/foodcards/foodcards.dart b/lib/models/foodcards/foodcards.dart
new file mode 100644
index 0000000..e69de29
diff --git a/lib/models/users/user.dart b/lib/models/users/user.dart
new file mode 100644
index 0000000..584e98f
--- /dev/null
+++ b/lib/models/users/user.dart
@@ -0,0 +1,95 @@
+import 'package:cloud_firestore/cloud_firestore.dart';
+import 'package:firebase_auth/firebase_auth.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+class UserProfile {
+ late String userId = FirebaseAuth.instance.currentUser!.uid;
+ late String fullName = '';
+ late String email = '';
+ late String contactNumber = '';
+ late String profileImageUrl = '';
+ late String role = '';
+ late String organisation = '';
+
+ static final UserProfile _instance = UserProfile._internal();
+
+ factory UserProfile() {
+ return _instance;
+ }
+
+ UserProfile._internal() {
+ loadUserProfile();
+ }
+
+ Future loadUserProfile() async {
+ SharedPreferences prefs = await SharedPreferences.getInstance();
+ userId = prefs.getString('userId') ?? '';
+ fullName = prefs.getString('fullName') ?? '';
+ email = prefs.getString('email') ?? '';
+ contactNumber = prefs.getString('contactNumber') ?? '';
+
+ // Fetch role and organisation from Firestore
+ await loadUserDataFromFirestore();
+
+ // Save profileImageUrl to instance variable
+ profileImageUrl = prefs.getString('profileImageUrl') ?? '';
+
+ // Rest of the method remains unchanged
+ }
+
+ Future loadUserDataFromFirestore() async {
+ try {
+ CollectionReference users =
+ FirebaseFirestore.instance.collection('users');
+ String userId = FirebaseAuth.instance.currentUser!.uid;
+
+ DocumentSnapshot userDoc = await users.doc(userId).get();
+
+ role = userDoc['role'];
+ organisation = userDoc['organisation'];
+
+ // Save role and organisation to SharedPreferences
+ saveRoleAndOrganisationToSharedPreferences();
+ } catch (e) {
+ print('Error loading user data from Firestore: $e');
+ }
+ }
+
+ void saveRoleAndOrganisationToSharedPreferences() async {
+ SharedPreferences prefs = await SharedPreferences.getInstance();
+ prefs.setString('role', role);
+ prefs.setString('organisation', organisation);
+ }
+
+ bool isAuthenticated() {
+ return email.isNotEmpty;
+ }
+
+ String getFullName() {
+ return fullName;
+ }
+
+ String getEmail() {
+ return email;
+ }
+
+ String getContactNumber() {
+ return contactNumber;
+ }
+
+ String getUserId() {
+ return userId;
+ }
+
+ String getProfileImageUrl() {
+ return profileImageUrl;
+ }
+
+ String getRole() {
+ return role;
+ }
+
+ String getOrganisation() {
+ return organisation;
+ }
+}
diff --git a/lib/screens/auth/email_signup.dart b/lib/screens/auth/email_signup.dart
new file mode 100644
index 0000000..0e6e48a
--- /dev/null
+++ b/lib/screens/auth/email_signup.dart
@@ -0,0 +1,406 @@
+import 'package:flutter/material.dart';
+import 'package:spoonshare/controllers/auth/signup_controller.dart';
+import 'package:spoonshare/screens/auth/signin.dart';
+import 'package:spoonshare/screens/home/home.dart';
+import 'package:spoonshare/widgets/loader.dart';
+import 'package:spoonshare/widgets/snackbar.dart';
+
+class EmailSignUpScreen extends StatefulWidget {
+ const EmailSignUpScreen({Key? key}) : super(key: key);
+
+ @override
+ _EmailSignUpScreenState createState() => _EmailSignUpScreenState();
+}
+
+class _EmailSignUpScreenState extends State {
+ final SignUpController _signUpController = SignUpController();
+ final TextEditingController _fullNameController = TextEditingController();
+ final TextEditingController _emailController = TextEditingController();
+ final TextEditingController _contactNumberController =
+ TextEditingController();
+ final TextEditingController _passwordController = TextEditingController();
+ final TextEditingController _confirmPasswordController =
+ TextEditingController();
+
+ bool _isPasswordVisible = false;
+ bool _isConfirmPasswordVisible = false;
+
+ @override
+ void dispose() {
+ _fullNameController.dispose();
+ _emailController.dispose();
+ _contactNumberController.dispose();
+ _passwordController.dispose();
+ _confirmPasswordController.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: SingleChildScrollView(
+ child: Center(
+ child: Container(
+ width: 360,
+ height: MediaQuery.of(context).size.height + 100,
+ clipBehavior: Clip.antiAlias,
+ decoration: const BoxDecoration(color: Colors.white),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Container(
+ width: 168,
+ height: 32,
+ margin: const EdgeInsets.only(top: 8),
+ padding: const EdgeInsets.symmetric(
+ horizontal: 16,
+ vertical: 8,
+ ),
+ decoration: BoxDecoration(
+ color: Colors.black.withOpacity(0.86),
+ borderRadius: BorderRadius.circular(50),
+ ),
+ child: const Center(
+ child: Text(
+ 'MEALS OF GRACE BY',
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 15,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w700,
+ height: 0,
+ ),
+ )),
+ ),
+ const Text(
+ 'SpoonShare',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 42,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w800,
+ ),
+ ),
+ const SizedBox(height: 8),
+ const Text(
+ 'अब भूखे नहीं रहेंगे.',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 20,
+ fontFamily: 'Asar',
+ fontWeight: FontWeight.w400,
+ ),
+ ),
+ ],
+ ),
+ SizedBox(
+ width: 275,
+ height: 66,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ const Text(
+ 'Welcome To Help!',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 24,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w700,
+ height: 0,
+ ),
+ ),
+ const SizedBox(height: 6),
+ SizedBox(
+ width: 275,
+ child: Text(
+ 'Find Free Food Near You / Donate food by entering details.',
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ color: Colors.black.withOpacity(0.800000011920929),
+ fontSize: 14,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w500,
+ height: 0,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: 32),
+ Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ InputField(
+ label: 'Full Name',
+ controller: _fullNameController,
+ ),
+ const SizedBox(height: 16),
+ InputField(label: 'Email', controller: _emailController),
+ const SizedBox(height: 16),
+ InputField(
+ label: 'Contact Number',
+ controller: _contactNumberController),
+ const SizedBox(height: 16),
+ InputField(
+ label: 'Password',
+ isPassword: true,
+ controller: _passwordController,
+ isPasswordVisible: _isPasswordVisible,
+ togglePasswordVisibility: () {
+ _togglePasswordVisibility();
+ },
+ ),
+ const SizedBox(height: 16),
+ InputField(
+ label: 'Confirm Password',
+ isPassword: true,
+ controller: _confirmPasswordController,
+ isPasswordVisible: _isConfirmPasswordVisible,
+ togglePasswordVisibility: () {
+ _toggleConfirmPasswordVisibility();
+ },
+ ),
+ ],
+ ),
+ const SizedBox(height: 16),
+ Container(
+ width: 309,
+ height: 32,
+ child: const Text.rich(
+ TextSpan(
+ children: [
+ TextSpan(
+ text: 'By ',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 12,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w400,
+ ),
+ ),
+ TextSpan(
+ text: 'Registering',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 12,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ TextSpan(
+ text: ' or ',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 12,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w400,
+ ),
+ ),
+ TextSpan(
+ text: 'Login',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 12,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ TextSpan(
+ text: ' you have agreed to these ',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 12,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w400,
+ ),
+ ),
+ TextSpan(
+ text: 'Terms and Conditions.',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 12,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ ],
+ ),
+ textAlign: TextAlign.center,
+ ),
+ ),
+ Container(
+ margin: const EdgeInsets.only(
+ top: 20.0), // Adjust the top margin as needed
+ child: GestureDetector(
+ onTap: () {
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) =>
+ const SignInScreen()), // Replace LoginScreen with the actual screen you want to navigate to
+ );
+ },
+ child: Text.rich(
+ TextSpan(
+ children: [
+ TextSpan(
+ text: 'Already have an account?',
+ style: TextStyle(
+ color:
+ Colors.black.withOpacity(0.699999988079071),
+ fontSize: 16,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w500,
+ height: 0,
+ ),
+ ),
+ const TextSpan(
+ text: ' ',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 16,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w500,
+ height: 0,
+ ),
+ ),
+ const TextSpan(
+ text: 'Log in',
+ style: TextStyle(
+ color: Color(0xFF0081DF),
+ fontSize: 16,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w700,
+ decoration: TextDecoration.underline,
+ height: 0,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ const SizedBox(height: 32),
+ ElevatedButton(
+ onPressed: () async {
+ // Show loading indicator
+ showLoadingDialog(context);
+
+ try {
+ // Perform signup asynchronously
+ await _signUpController.signUp(
+ fullName: _fullNameController.text,
+ email: _emailController.text,
+ contactNumber: _contactNumberController.text,
+ password: _passwordController.text,
+ confirmPassword: _confirmPasswordController.text,
+ context: context,
+ );
+
+ // Hide loading indicator
+ Navigator.of(context).pop(); // Pop the loading dialog
+
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) => const HomeScreen()),
+ );
+ } catch (e) {
+ // Handle any exceptions during signup
+ print("Signup failed: $e");
+
+ // Hide loading indicator
+ Navigator.of(context).pop(); // Pop the loading dialog
+
+ // You can customize this part based on your requirements
+ showErrorSnackbar(
+ context, 'Signup failed. Please try again.');
+ }
+ },
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Colors.black,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(50),
+ ),
+ ),
+ child: const SizedBox(
+ width: 312,
+ height: 45,
+ child: Center(
+ child: Text(
+ 'Create Account',
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 18,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w500,
+ letterSpacing: 0.36,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ void _togglePasswordVisibility() {
+ setState(() {
+ _isPasswordVisible = !_isPasswordVisible;
+ });
+ }
+
+ void _toggleConfirmPasswordVisibility() {
+ setState(() {
+ _isConfirmPasswordVisible = !_isConfirmPasswordVisible;
+ });
+ }
+}
+
+class InputField extends StatelessWidget {
+ final String label;
+ final bool isPassword;
+ final TextEditingController controller;
+ final bool? isPasswordVisible;
+ final VoidCallback? togglePasswordVisibility;
+
+ const InputField({
+ Key? key,
+ required this.label,
+ this.isPassword = false,
+ required this.controller,
+ this.isPasswordVisible,
+ this.togglePasswordVisibility,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return TextField(
+ controller: controller, // Ensure the controller is assigned here
+ obscureText: isPassword && isPasswordVisible != true,
+ decoration: InputDecoration(
+ labelText: label,
+ border: const OutlineInputBorder(),
+ suffixIcon: isPassword && togglePasswordVisibility != null
+ ? IconButton(
+ icon: Icon(
+ isPasswordVisible! ? Icons.visibility : Icons.visibility_off,
+ ),
+ onPressed: togglePasswordVisibility,
+ )
+ : null,
+ ),
+ );
+ }
+}
diff --git a/lib/screens/auth/forgot_password.dart b/lib/screens/auth/forgot_password.dart
new file mode 100644
index 0000000..01a78e0
--- /dev/null
+++ b/lib/screens/auth/forgot_password.dart
@@ -0,0 +1,179 @@
+import 'package:flutter/material.dart';
+import 'package:spoonshare/screens/auth/signin.dart';
+import 'package:spoonshare/services/forgot_password.dart';
+import 'package:spoonshare/widgets/loader.dart';
+import 'package:spoonshare/widgets/snackbar.dart';
+
+class ForgotPasswordScreen extends StatelessWidget {
+ final ForgotPasswordService _forgotPasswordService = ForgotPasswordService();
+ final TextEditingController _emailController = TextEditingController();
+
+ ForgotPasswordScreen({Key? key}) : super(key: key);
+
+ void _onSubmitClick(BuildContext context) async {
+ String email = _emailController.text.trim();
+
+ if (email.isEmpty) {
+ showErrorSnackbar(context, 'Please enter your email.');
+ return;
+ }
+ RegExp emailRegex = RegExp(
+ r'^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$',
+ );
+
+ if (!emailRegex.hasMatch(email)) {
+ showErrorSnackbar(context, 'Please enter a valid email.');
+ return;
+ }
+
+ try {
+ showLoadingDialog(context);
+ await _forgotPasswordService.resetPassword(email);
+ showSuccessSnackbar(context, 'Password reset email sent successfully.');
+ } catch (e) {
+ showErrorSnackbar(context, ('Error: $e'));
+ } finally {
+ Navigator.pop(context);
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: Center(
+ child: Container(
+ width: MediaQuery.of(context).size.width - 40,
+ margin: const EdgeInsets.all(40.0),
+ clipBehavior: Clip.antiAlias,
+ decoration: const BoxDecoration(color: Colors.white),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ const Text(
+ 'Reset your password',
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 24,
+ fontFamily: 'Poppins',
+ fontWeight: FontWeight.w600,
+ height: 1.2,
+ letterSpacing: -0.53,
+ ),
+ ),
+ const SizedBox(height: 20),
+ const Text(
+ 'Enter your registered email below',
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 14,
+ fontFamily: 'Poppins',
+ fontWeight: FontWeight.w400,
+ height: 1.2,
+ letterSpacing: -0.29,
+ ),
+ ),
+ const SizedBox(height: 20),
+ Container(
+ width: double.infinity,
+ height: 55,
+ margin: const EdgeInsets.only(bottom: 20.0),
+ decoration: const ShapeDecoration(
+ shape: RoundedRectangleBorder(
+ side: BorderSide(width: 1),
+ borderRadius: BorderRadius.all(Radius.circular(100)),
+ ),
+ ),
+ child: TextFormField(
+ controller: _emailController,
+ decoration: const InputDecoration(
+ hintText: 'Email',
+ contentPadding: EdgeInsets.symmetric(horizontal: 20),
+ border: InputBorder.none,
+ ),
+ ),
+ ),
+ Container(
+ width: double.infinity,
+ height: 57,
+ decoration: const ShapeDecoration(
+ color: Color(0xFFFBDE3F),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.all(Radius.circular(100)),
+ ),
+ ),
+ child: ElevatedButton(
+ onPressed: () => _onSubmitClick(context),
+ style: ElevatedButton.styleFrom(
+ backgroundColor:
+ const Color(0xFFFBDE3F), // Background color
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(100),
+ ),
+ ),
+ child: const Center(
+ child: Text(
+ 'Submit',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 17,
+ fontFamily: 'Poppins',
+ fontWeight: FontWeight.w600,
+ height: 1.2,
+ letterSpacing: -0.37,
+ ),
+ ),
+ ),
+ ),
+ ),
+ const SizedBox(height: 40),
+ SizedBox(
+ width: 215,
+ child: GestureDetector(
+ onTap: () {
+ Navigator.of(context).pushReplacement(
+ MaterialPageRoute(
+ builder: (context) => const SignInScreen()),
+ );
+ },
+ child: const Text.rich(
+ TextSpan(
+ children: [
+ TextSpan(
+ text: 'Remember the password? ',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 14,
+ fontFamily: 'Poppins',
+ fontWeight: FontWeight.w400,
+ height: 1.2,
+ letterSpacing: -0.29,
+ ),
+ ),
+ TextSpan(
+ text: 'Login',
+ style: TextStyle(
+ color: Colors.blue,
+ fontSize: 15,
+ fontFamily: 'Poppins',
+ fontWeight: FontWeight.w600,
+ decoration: TextDecoration.underline,
+ height: 1.2,
+ letterSpacing: -0.29,
+ ),
+ ),
+ ],
+ ),
+ textAlign: TextAlign.center,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/screens/auth/signin.dart b/lib/screens/auth/signin.dart
new file mode 100644
index 0000000..ad875d1
--- /dev/null
+++ b/lib/screens/auth/signin.dart
@@ -0,0 +1,461 @@
+import 'package:flutter/material.dart';
+import 'package:spoonshare/controllers/auth/signin_controller.dart';
+import 'package:spoonshare/screens/auth/forgot_password.dart';
+import 'package:spoonshare/screens/auth/signup.dart';
+import 'package:spoonshare/screens/home/home.dart';
+import 'package:spoonshare/widgets/loader.dart';
+import 'package:spoonshare/widgets/snackbar.dart';
+
+class SignInScreen extends StatefulWidget {
+ const SignInScreen({super.key});
+
+ @override
+ State createState() => _SignInScreenState();
+}
+
+class _SignInScreenState extends State {
+ final SignInController _signInController = SignInController();
+ final TextEditingController _emailController = TextEditingController();
+
+ final TextEditingController _passwordController = TextEditingController();
+ bool _isPasswordVisible = false;
+
+ @override
+ void dispose() {
+ _emailController.dispose();
+ _passwordController.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: SingleChildScrollView(
+ child: Center(
+ child: Container(
+ padding: const EdgeInsets.all(16),
+ margin: const EdgeInsets.only(top: 40),
+ constraints: const BoxConstraints(
+ maxWidth: 600, // Adjust the maximum width as needed
+ ),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Container(
+ width: 168,
+ height: 32,
+ margin: const EdgeInsets.only(top: 8),
+ padding: const EdgeInsets.symmetric(
+ horizontal: 16,
+ vertical: 8,
+ ),
+ decoration: BoxDecoration(
+ color: Colors.black.withOpacity(0.86),
+ borderRadius: BorderRadius.circular(50),
+ ),
+ child: const Center(
+ child: Text(
+ 'MEALS OF GRACE BY',
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 15,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w700,
+ height: 0,
+ ),
+ )),
+ ),
+ const Text(
+ 'SpoonShare',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 42,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w800,
+ ),
+ ),
+ const SizedBox(height: 8),
+ const Text(
+ 'अब भूखे नहीं रहेंगे.',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 20,
+ fontFamily: 'Asar',
+ fontWeight: FontWeight.w400,
+ ),
+ ),
+ ],
+ ),
+ SizedBox(
+ width: 275,
+ height: 66,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ const Text(
+ 'Welcome To Help!',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 24,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w700,
+ height: 0,
+ ),
+ ),
+ const SizedBox(height: 6),
+ SizedBox(
+ width: 275,
+ child: Text(
+ 'Find Free Food Near You / Donate food by entering details.',
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ color: Colors.black.withOpacity(0.800000011920929),
+ fontSize: 14,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w500,
+ height: 0,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: 32),
+ Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ const SizedBox(height: 16),
+ InputField(label: 'Email', controller: _emailController),
+ const SizedBox(height: 16),
+ const SizedBox(height: 16),
+ InputField(
+ label: 'Password',
+ isPassword: true,
+ controller: _passwordController,
+ isPasswordVisible: _isPasswordVisible,
+ togglePasswordVisibility: () {
+ _togglePasswordVisibility();
+ },
+ ),
+ ],
+ ),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ GestureDetector(
+ onTap: () {
+ Navigator.of(context).pushReplacement(
+ MaterialPageRoute(builder: (context) => ForgotPasswordScreen()),
+ );
+ },
+ child: const Align(
+ alignment: Alignment.bottomRight,
+ child: Padding(
+ padding: EdgeInsets.only(top: 8.0, right: 8.0),
+ child: Text(
+ 'forgot password?',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 15,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w500,
+ textBaseline: TextBaseline.alphabetic,
+ height: 0,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(height: 16),
+ const SizedBox(
+ width: 309,
+ height: 32,
+ child: Text.rich(
+ TextSpan(
+ children: [
+ TextSpan(
+ text: 'By ',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 12,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w400,
+ ),
+ ),
+ TextSpan(
+ text: 'Registering',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 12,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ TextSpan(
+ text: ' or ',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 12,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w400,
+ ),
+ ),
+ TextSpan(
+ text: 'Login',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 12,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ TextSpan(
+ text: ' you have agreed to these ',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 12,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w400,
+ ),
+ ),
+ TextSpan(
+ text: 'Terms and Conditions.',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 12,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ ],
+ ),
+ textAlign: TextAlign.center,
+ ),
+ ),
+ Container(
+ margin: const EdgeInsets.only(
+ top: 20.0), // Adjust the top margin as needed
+ child: Container(
+ child: GestureDetector(
+ onTap: () {
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) => SignUpScreen()),
+ );
+ },
+ child: Text.rich(
+ TextSpan(
+ children: [
+ TextSpan(
+ text: 'Don\'t have an account?',
+ style: TextStyle(
+ color:
+ Colors.black.withOpacity(0.699999988079071),
+ fontSize: 16,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w500,
+ height: 0,
+ ),
+ ),
+ const TextSpan(
+ text: ' ',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 16,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w500,
+ height: 0,
+ ),
+ ),
+ const TextSpan(
+ text: 'Sign Up',
+ style: TextStyle(
+ color: Color(0xFF0081DF),
+ fontSize: 16,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w700,
+ decoration: TextDecoration.underline,
+ height: 0,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ),
+ const SizedBox(height: 32),
+ ElevatedButton(
+ onPressed: () async {
+ // Show loading indicator
+ showLoadingDialog(context);
+
+ try {
+ // Perform signup asynchronously
+ await _signInController.signIn(
+ email: _emailController.text,
+ password: _passwordController.text,
+ context: context,
+ );
+ // Hide loading indicator
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) => const HomeScreen()),
+ );
+ } catch (e) {
+ // Handle any exceptions during signup
+ print("Signup failed: $e");
+ // Hide loading indicator
+ // You can customize this part based on your requirements
+ showErrorSnackbar(
+ context, 'Signin failed. Please try again.');
+ }
+ },
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Colors.black,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(50),
+ ),
+ ),
+ child: const SizedBox(
+ width: 312,
+ height: 45,
+ child: Center(
+ child: Text(
+ 'Log In',
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 18,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w500,
+ letterSpacing: 0.36,
+ ),
+ ),
+ ),
+ ),
+ ),
+
+ const SizedBox(height: 16),
+ const Center(
+ child: Column(
+ children: [
+ Text(
+ 'Or',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 16,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w500,
+ height: 4,
+ ),
+ ),
+ ],
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.only(top: 10.0), // Adjust the top padding as needed
+ child: Container(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 41.50, vertical: 12),
+ decoration: ShapeDecoration(
+ color: Colors.black.withOpacity(0.07999999821186066),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(50),
+ ),
+ ),
+ child: InkWell(
+ onTap: () {
+ _signInController.signInWithGoogle(context);
+ },
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Container(
+ width: 24,
+ height: 24,
+ decoration: const BoxDecoration(
+ image: DecorationImage(
+ image: AssetImage("assets/images/google.png"),
+ fit: BoxFit.fill,
+ ),
+ ),
+ ),
+ const SizedBox(width: 12),
+ const Text(
+ 'Sign In with Google',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 18,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w700,
+ height: 0,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ void _togglePasswordVisibility() {
+ setState(() {
+ _isPasswordVisible = !_isPasswordVisible;
+ });
+ }
+}
+
+class InputField extends StatelessWidget {
+ final String label;
+ final bool isPassword;
+ final TextEditingController controller;
+ final bool? isPasswordVisible;
+ final VoidCallback? togglePasswordVisibility;
+
+ const InputField({
+ Key? key,
+ required this.label,
+ this.isPassword = false,
+ required this.controller,
+ this.isPasswordVisible,
+ this.togglePasswordVisibility,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return TextField(
+ controller: controller, // Ensure the controller is assigned here
+ obscureText: isPassword && isPasswordVisible != true,
+ decoration: InputDecoration(
+ labelText: label,
+ border: const OutlineInputBorder(),
+ suffixIcon: isPassword && togglePasswordVisibility != null
+ ? IconButton(
+ icon: Icon(
+ isPasswordVisible! ? Icons.visibility : Icons.visibility_off,
+ ),
+ onPressed: togglePasswordVisibility,
+ )
+ : null,
+ ),
+ );
+ }
+}
diff --git a/lib/screens/auth/signup.dart b/lib/screens/auth/signup.dart
new file mode 100644
index 0000000..a876022
--- /dev/null
+++ b/lib/screens/auth/signup.dart
@@ -0,0 +1,297 @@
+import 'package:flutter/material.dart';
+import 'package:spoonshare/controllers/auth/signup_controller.dart';
+import 'package:spoonshare/screens/auth/email_signup.dart';
+import 'package:spoonshare/screens/auth/signin.dart';
+
+class SignUpScreen extends StatelessWidget {
+ final SignUpController signUpController = SignUpController();
+
+ SignUpScreen({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: Center(
+ child: Container(
+ width: 360,
+ height: 800,
+ clipBehavior: Clip.antiAlias,
+ decoration: const BoxDecoration(color: Colors.white),
+ child: Stack(
+ children: [
+ Positioned(
+ left: 65,
+ top: 65,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Container(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 16, vertical: 8),
+ decoration: ShapeDecoration(
+ color: Colors.black.withOpacity(0.8600000143051147),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(50),
+ ),
+ ),
+ child: const Row(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Text(
+ 'MEALS OF GRACE BY',
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 14,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w700,
+ height: 0,
+ ),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(height: 4),
+ const Text(
+ 'SpoonShare',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 42,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w800,
+ height: 0,
+ ),
+ ),
+ const SizedBox(height: 4),
+ const Text(
+ 'अब भूखे नहीं रहेंगे.',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 20,
+ fontFamily: 'Asar',
+ fontWeight: FontWeight.w400,
+ height: 0,
+ ),
+ ),
+ ],
+ ),
+ ),
+ Positioned(
+ left: 43,
+ top: 224,
+ child: Container(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ const Text(
+ 'Welcome To Help!',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 24,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w700,
+ height: 0,
+ ),
+ ),
+ const SizedBox(height: 6),
+ SizedBox(
+ width: 275,
+ child: Text(
+ 'Find Free Food Near You / Donate food by entering details.',
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ color: Colors.black.withOpacity(0.800000011920929),
+ fontSize: 14,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w500,
+ height: 0,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ Positioned(
+ left: 65,
+ top: 570,
+ child: GestureDetector(
+ onTap: () {
+ // Navigate to the login screen when tapped
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) =>
+ const SignInScreen()), // Replace LoginScreen with the actual screen you want to navigate to
+ );
+ },
+ child: Text.rich(
+ TextSpan(
+ children: [
+ TextSpan(
+ text: 'Already have an account?',
+ style: TextStyle(
+ color: Colors.black.withOpacity(0.699999988079071),
+ fontSize: 16,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w500,
+ height: 0,
+ ),
+ ),
+ const TextSpan(
+ text: ' ',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 16,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w500,
+ height: 0,
+ ),
+ ),
+ const TextSpan(
+ text: 'Log in',
+ style: TextStyle(
+ color: Color(0xFF0081DF),
+ fontSize: 16,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w700,
+ decoration: TextDecoration.underline,
+ height: 0,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ const Positioned(
+ left: 43,
+ top: 362,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Text(
+ 'Continue with',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 20,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w700,
+ height: 0,
+ ),
+ textAlign: TextAlign.center,
+ ),
+ ],
+ ),
+ ),
+ Positioned(
+ left: 39,
+ top: 419,
+ child: Container(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 48, vertical: 12),
+ decoration: ShapeDecoration(
+ color: Colors.black.withOpacity(0.07999999821186066),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(50),
+ ),
+ ),
+ child: InkWell(
+ onTap: () {
+ // Navigate to another screen
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) => const EmailSignUpScreen()),
+ );
+ },
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Container(
+ width: 24,
+ height: 24,
+ decoration: const BoxDecoration(
+ image: DecorationImage(
+ image: AssetImage("assets/images/mail.png"),
+ fit: BoxFit.fill,
+ ),
+ ),
+ ),
+ const SizedBox(width: 12),
+ const Text(
+ 'Sign up with email',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 18,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w700,
+ height: 0,
+ ),
+ ),
+ ],
+ ),
+ ),
+ )),
+ Positioned(
+ left: 39,
+ top: 491,
+ child: Container(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 41.50, vertical: 12),
+ decoration: ShapeDecoration(
+ color: Colors.black.withOpacity(0.07999999821186066),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(50),
+ ),
+ ),
+ child: InkWell(
+ onTap: () {
+ signUpController.signUpWithGoogle(context);
+ },
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Container(
+ width: 24,
+ height: 24,
+ decoration: const BoxDecoration(
+ image: DecorationImage(
+ image: AssetImage("assets/images/google.png"),
+ fit: BoxFit.fill,
+ ),
+ ),
+ ),
+ const SizedBox(width: 12),
+ const Text(
+ 'Sign up with Google',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 18,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w700,
+ height: 0,
+ ),
+ ),
+ ],
+ ),
+ ),
+ )),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/screens/chat/chat_page.dart b/lib/screens/chat/chat_page.dart
new file mode 100644
index 0000000..5e243c4
--- /dev/null
+++ b/lib/screens/chat/chat_page.dart
@@ -0,0 +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(),
+ );
+ }
+}
diff --git a/lib/screens/donate/donate_food.dart b/lib/screens/donate/donate_food.dart
new file mode 100644
index 0000000..8e8a834
--- /dev/null
+++ b/lib/screens/donate/donate_food.dart
@@ -0,0 +1,22 @@
+import 'package:flutter/material.dart';
+
+class DonateFoodScreenContent extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ // Add your DonateScreen content here
+ return const Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SizedBox(height: 16),
+ Text(
+ 'Donate Screen Content',
+ style: TextStyle(
+ fontSize: 18,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ // Add more widgets as needed
+ ],
+ );
+ }
+}
diff --git a/lib/screens/donate/donate_page.dart b/lib/screens/donate/donate_page.dart
new file mode 100644
index 0000000..1e27f15
--- /dev/null
+++ b/lib/screens/donate/donate_page.dart
@@ -0,0 +1,152 @@
+import 'package:flutter/material.dart';
+import 'package:spoonshare/screens/donate/donate_food.dart';
+import 'package:spoonshare/screens/donate/share_food.dart';
+import 'package:spoonshare/widgets/bottom_navbar.dart';
+
+class DonatePage extends StatefulWidget {
+ const DonatePage({Key? key}) : super(key: key);
+
+ @override
+ _DonatePageState createState() => _DonatePageState();
+}
+
+class _DonatePageState extends State {
+ bool isShareFoodSelected = true;
+ bool isDonateFoodSelected = false;
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: SingleChildScrollView(
+ child: Padding(
+ padding: const EdgeInsets.all(18.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const SizedBox(height: 30),
+ const Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ 'Donor Page',
+ style: TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ const SizedBox(height: 8),
+ Container(
+ width: 360,
+ decoration: ShapeDecoration(
+ shape: RoundedRectangleBorder(
+ side: BorderSide(
+ width: 2,
+ color: Colors.black.withOpacity(0.1),
+ ),
+ borderRadius: BorderRadius.circular(15),
+ ),
+ ),
+ ),
+ const SizedBox(height: 8),
+ Container(
+ width: 380,
+ margin: const EdgeInsets.only(top: 20),
+ decoration: ShapeDecoration(
+ shape: RoundedRectangleBorder(
+ side: BorderSide(
+ width: 2,
+ color: Colors.black.withOpacity(0.1),
+ ),
+ borderRadius: BorderRadius.circular(15),
+ ),
+ ),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ children: [
+ GestureDetector(
+ onTap: () {
+ setState(() {
+ isShareFoodSelected = true;
+ isDonateFoodSelected = false;
+ });
+ },
+ child: Container(
+ width: 180,
+ height: 40,
+ decoration: ShapeDecoration(
+ color: isShareFoodSelected ? Colors.black : Colors.white,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(50),
+ ),
+ ),
+ child: Center(
+ child: Text(
+ 'Share Food',
+ style: TextStyle(
+ color: isShareFoodSelected ? Colors.white : Colors.black,
+ fontSize: 16,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w700,
+ height: 0,
+ ),
+ ),
+ ),
+ ),
+ ),
+ GestureDetector(
+ onTap: () {
+ setState(() {
+ isShareFoodSelected = false;
+ isDonateFoodSelected = true;
+ });
+ },
+ child: Container(
+ width: 180,
+ height: 40,
+ decoration: ShapeDecoration(
+ color: isDonateFoodSelected ? Colors.black : Colors.white,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(50),
+ ),
+ ),
+ child: Center(
+ child: Text(
+ 'Donate Food',
+ style: TextStyle(
+ color: isDonateFoodSelected ? Colors.white : Colors.black,
+ fontSize: 16,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w700,
+ height: 0,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ // Conditional rendering of ShareFoodScreen content
+ if (isShareFoodSelected) ...[
+ ShareFoodScreenContent(),
+ ],
+ // Conditional rendering of DonateScreen content
+ if (isDonateFoodSelected) ...[
+ DonateFoodScreenContent(),
+ ],
+ ],
+ ),
+ ),
+ ),
+ bottomNavigationBar: const BottomNavBar(),
+ );
+ }
+
+}
diff --git a/lib/screens/donate/share_food.dart b/lib/screens/donate/share_food.dart
new file mode 100644
index 0000000..0a8c80a
--- /dev/null
+++ b/lib/screens/donate/share_food.dart
@@ -0,0 +1,493 @@
+// ignore_for_file: use_build_context_synchronously
+
+import 'dart:io';
+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:permission_handler/permission_handler.dart';
+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/custom_text_field.dart';
+import 'package:spoonshare/widgets/loader.dart';
+import 'package:spoonshare/widgets/snackbar.dart';
+import 'package:firebase_storage/firebase_storage.dart' as firebase_storage;
+
+class ShareFoodScreenContent extends StatefulWidget {
+ @override
+ _ShareFoodScreenContentState createState() => _ShareFoodScreenContentState();
+}
+
+class _ShareFoodScreenContentState extends State {
+ final TextEditingController _imageController = TextEditingController();
+ final TextEditingController _venueController = TextEditingController();
+ final TextEditingController _addressController = TextEditingController();
+ final TextEditingController _communityController = TextEditingController();
+ final TextEditingController _dateController = TextEditingController();
+ final TextEditingController _timeController = TextEditingController();
+ final TextEditingController _toDateController = TextEditingController();
+ final TextEditingController _toTimeController = TextEditingController();
+
+ File? _imageFile;
+ String _selectedFoodType = '';
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const SizedBox(height: 20),
+ _buildImageUploadBox(),
+ const SizedBox(height: 16),
+ CustomTextField(
+ label: 'Venue',
+ controller: _venueController,
+ ),
+ const SizedBox(height: 16),
+ CustomTextField(
+ label: 'Enter Address',
+ controller: _addressController,
+ ),
+ const SizedBox(height: 16),
+ CustomTextField(
+ label: 'For whom it is? (Commuity)',
+ controller: _communityController,
+ ),
+ const SizedBox(height: 16),
+ _buildDateAndTimeInputs(context),
+ const SizedBox(height: 16),
+ _buildDropdownInput(),
+ const SizedBox(height: 16),
+ _buildSubmitButton(),
+ ],
+ );
+ }
+
+Widget _buildDateAndTimeInputs(BuildContext context) {
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ children: [
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const Text('From Date'),
+ TextField(
+ controller: _dateController,
+ readOnly: true,
+ onTap: () async {
+ DateTime? selectedDate = await _selectDate(context, _dateController);
+ if (selectedDate != null) {
+ _dateController.text = selectedDate.toLocal().toString().split(' ')[0];
+ }
+ },
+ decoration: const InputDecoration(
+ hintText: 'Select Date',
+ border: OutlineInputBorder(),
+ ),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(width: 16),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const Text('From Time'),
+ TextField(
+ controller: _timeController,
+ readOnly: true,
+ onTap: () async {
+ TimeOfDay? selectedTime = await _selectTime(context, _timeController);
+ if (selectedTime != null) {
+ _timeController.text = selectedTime.format(context);
+ }
+ },
+ decoration: const InputDecoration(
+ hintText: 'Select Time',
+ border: OutlineInputBorder(),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(height: 16),
+ Row(
+ children: [
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const Text('To Date'),
+ TextField(
+ controller: _toDateController,
+ readOnly: true,
+ onTap: () async {
+ DateTime? selectedDate = await _selectDate(context, _toDateController);
+ if (selectedDate != null) {
+ _toDateController.text = selectedDate.toLocal().toString().split(' ')[0];
+ }
+ },
+ decoration: const InputDecoration(
+ hintText: 'Select Date',
+ border: OutlineInputBorder(),
+ ),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(width: 16),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const Text('To Time'),
+ TextField(
+ controller: _toTimeController,
+ readOnly: true,
+ onTap: () async {
+ TimeOfDay? selectedTime = await _selectTime(context, _toTimeController);
+ if (selectedTime != null) {
+ _toTimeController.text = selectedTime.format(context);
+ }
+ },
+ decoration: const InputDecoration(
+ hintText: 'Select Time',
+ border: OutlineInputBorder(),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ],
+ );
+}
+
+Future _selectDate(
+ BuildContext context, TextEditingController controller) async {
+ final DateTime? picked = await showDatePicker(
+ context: context,
+ initialDate: DateTime.now(),
+ firstDate: DateTime.now(),
+ lastDate: DateTime.now().add(const Duration(days: 365)),
+ );
+
+ if (picked != null) {
+ controller.text = picked.toLocal().toString().split(' ')[0];
+ }
+
+ return picked;
+}
+
+Future _selectTime(
+ BuildContext context, TextEditingController controller) async {
+ final TimeOfDay? picked = await showTimePicker(
+ context: context,
+ initialTime: TimeOfDay.now(),
+ );
+
+ if (picked != null) {
+ controller.text = picked.format(context);
+ }
+
+ return picked;
+}
+
+ Widget _buildImageUploadBox() {
+ return Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ InkWell(
+ onTap: () {
+ pickFile();
+ },
+ child: Container(
+ width: 280,
+ height: 180,
+ decoration: ShapeDecoration(
+ color: Colors.black.withOpacity(0.07999999821186066),
+ shape: RoundedRectangleBorder(
+ side: BorderSide(
+ width: 1,
+ color: Colors.black.withOpacity(0.6000000238418579),
+ ),
+ borderRadius: BorderRadius.circular(10),
+ ),
+ ),
+ child: _imageFile == null
+ ? const Icon(
+ Icons.camera_alt,
+ size: 48,
+ color: Colors.grey,
+ )
+ : Image.file(
+ _imageFile!,
+ width: 48, // Adjust the size as needed
+ height: 48, // Adjust the size as needed
+ fit: BoxFit.cover,
+ ),
+ ),
+ ),
+ const SizedBox(height: 8),
+ const Padding(
+ padding: EdgeInsets.symmetric(horizontal: 16),
+ child: Text(
+ '* Fill below details to share food',
+ style: TextStyle(
+ color: Colors.grey,
+ fontSize: 16,
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ Future pickFile() async {
+ final androidInfo = await DeviceInfoPlugin().androidInfo;
+ late final Map status;
+ if (androidInfo.version.sdkInt <= 32) {
+ status = await [
+ Permission.storage,
+ Permission
+ .camera, // Request camera permission for devices with SDK <= 32
+ ].request();
+ } else {
+ status = await [
+ Permission.photos,
+ Permission.camera,
+ Permission.notification,
+ ].request();
+ }
+
+ var allAccepted = true;
+ status.forEach((permission, status) {
+ if (status != PermissionStatus.granted) {
+ allAccepted = false;
+ }
+ });
+
+ if (allAccepted) {
+ // Show options for gallery or camera
+ showModalBottomSheet(
+ context: context,
+ builder: (BuildContext context) {
+ return Wrap(
+ children: [
+ ListTile(
+ leading: const Icon(Icons.photo),
+ title: const Text('Pick from Gallery'),
+ onTap: () async {
+ Navigator.of(context).pop();
+ await _pickImage(ImageSource.gallery);
+ },
+ ),
+ ListTile(
+ leading: const Icon(Icons.camera),
+ title: const Text('Capture with Camera'),
+ onTap: () async {
+ Navigator.of(context).pop();
+ await _pickImage(ImageSource.camera);
+ },
+ ),
+ ],
+ );
+ },
+ );
+ } else {
+ print('Storage or camera permission denied');
+ }
+ }
+
+ Future _pickImage(ImageSource source) async {
+ XFile? pickedImage = await ImagePicker().pickImage(
+ source: source,
+ );
+
+ if (pickedImage != null) {
+ _imageFile = File(pickedImage.path);
+ _imageController.text = _imageFile!.path;
+ setState(() {});
+ }
+ }
+
+ Widget _buildSubmitButton() {
+ double screenWidth = MediaQuery.of(context).size.width;
+ double screenHeight = MediaQuery.of(context).size.height;
+
+ return Container(
+ width: screenWidth * 0.8667,
+ height: screenHeight * 0.05625,
+ margin: const EdgeInsets.only(top: 20),
+ decoration: BoxDecoration(
+ color: Colors.black,
+ borderRadius: BorderRadius.circular(50),
+ ),
+ child: InkWell(
+ onTap: () {
+ submitFood();
+ },
+ child: const Center(
+ child: Text(
+ 'Submit',
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 18,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w500,
+ letterSpacing: 0.36,
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ void submitFood() async {
+ // Check if all required fields are filled
+ if (_imageFile == null || _selectedFoodType.isEmpty) {
+ // Show an error message to the user
+ showErrorSnackbar(context, 'Please fill all required fields');
+ return;
+ }
+
+ try {
+ showLoadingDialog(context);
+
+ String userId = FirebaseAuth.instance.currentUser!.uid;
+ UserProfile userProfile = UserProfile();
+ String fullName = userProfile.getFullName();
+ String venue = _venueController.text;
+ String address = _addressController.text;
+ String community = _communityController.text;
+ String date = _dateController.text;
+ String time = _timeController.text;
+ String toDate = _toDateController.text;
+ String toTime = _toTimeController.text;
+
+ // Upload the image to Firebase Storage
+ String imageUrl = await uploadImageToFirebaseStorage(_imageFile, venue);
+
+ // Create a map with food details, including a timestamp
+ Map foodData = {
+ 'userId': userId,
+ 'fullName': fullName,
+ 'imageUrl': imageUrl,
+ 'venue': venue,
+ 'address': address,
+ 'community': community,
+ 'foodType': _selectedFoodType,
+ 'date': date,
+ 'time': time,
+ 'toDate': toDate,
+ 'toTime': toTime,
+ 'timestamp': FieldValue.serverTimestamp(),
+ };
+ // Save food data under the user's document in the 'sharedFood' collection
+ await FirebaseFirestore.instance
+ .collection('food')
+ .doc('sharedfood')
+ .collection("foodData")
+ .add(foodData);
+
+ Navigator.of(context).pop();
+ showSuccessSnackbar(context, 'Food submitted successfully!');
+ Navigator.push(context,
+ MaterialPageRoute(builder: (context) => const ThankYouScreen()));
+ } catch (e) {
+ print('Error submitting food: $e');
+ showErrorSnackbar(context, 'Error submitting food');
+ } finally {
+ _venueController.clear();
+ _addressController.clear();
+ _communityController.clear();
+ _imageController.clear();
+ _dateController.clear();
+ _timeController.clear();
+ _toDateController.clear();
+ _toTimeController.clear();
+ _imageFile = null;
+
+ setState(() {
+ _selectedFoodType = '';
+ });
+ }
+ }
+
+ Widget _buildDropdownInput() {
+ return Container(
+ width: double.infinity,
+ height: 46,
+ decoration: ShapeDecoration(
+ shape: RoundedRectangleBorder(
+ side: BorderSide(
+ width: 1.30,
+ color: Colors.black.withOpacity(0.6000000238418579),
+ ),
+ borderRadius: BorderRadius.circular(4),
+ ),
+ ),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: DropdownButton(
+ items: const [
+ DropdownMenuItem(
+ value: 'veg',
+ child: Text('Veg'),
+ ),
+ DropdownMenuItem(
+ value: 'nonveg',
+ child: Text('Non-Veg'),
+ ),
+ DropdownMenuItem(
+ value: 'both',
+ child: Text('Both'),
+ ),
+ ],
+ onChanged: (value) {
+ setState(() {
+ _selectedFoodType = value!;
+ });
+ },
+ value: _selectedFoodType.isNotEmpty ? _selectedFoodType : null,
+ hint: const Text('Food Type'),
+ style: const TextStyle(color: Colors.black),
+ isExpanded: true,
+ ),
+ ),
+ );
+ }
+
+ Future uploadImageToFirebaseStorage(
+ File? imageFile, String venue) async {
+ if (imageFile == null) {
+ throw Exception('Image file is null');
+ }
+
+ try {
+ String fileName = 'food/shared_food/$venue.jpg';
+
+ firebase_storage.Reference storageReference =
+ firebase_storage.FirebaseStorage.instance.ref().child(fileName);
+
+ await storageReference.putFile(imageFile);
+
+ String downloadURL = await storageReference.getDownloadURL();
+
+ return downloadURL;
+ } catch (e) {
+ print('Error uploading image to Firebase Storage: $e');
+ throw Exception('Error uploading image to Firebase Storage');
+ }
+ }
+}
diff --git a/lib/screens/donate/thank_you.dart b/lib/screens/donate/thank_you.dart
new file mode 100644
index 0000000..6bc6bad
--- /dev/null
+++ b/lib/screens/donate/thank_you.dart
@@ -0,0 +1,54 @@
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:spoonshare/screens/home/home.dart';
+import 'package:spoonshare/widgets/bottom_navbar.dart';
+
+class ThankYouScreen extends StatelessWidget {
+ const ThankYouScreen({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ Timer(const Duration(seconds: 5), () {
+ Navigator.of(context).pushReplacement(
+ MaterialPageRoute(
+ builder: (context) => const HomeScreen(),
+ ),
+ );
+ });
+
+ return Scaffold(
+ body: Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Image.asset(
+ 'assets/images/thankyou.jpg', // Add your thank you image asset path
+ height: 150,
+ width: 150,
+ // You can adjust the size based on your image dimensions
+ ),
+ const SizedBox(height: 20),
+ const Text(
+ 'Thank You!',
+ style: TextStyle(
+ fontSize: 24,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ const SizedBox(height: 10),
+ const Text(
+ 'Your contribution is greatly appreciated.',
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ fontSize: 16,
+ color: Colors.grey,
+ ),
+ ),
+ ],
+ ),
+ ),
+ bottomNavigationBar: const BottomNavBar(),
+ );
+ }
+}
diff --git a/lib/screens/fooddetails/food_details.dart b/lib/screens/fooddetails/food_details.dart
new file mode 100644
index 0000000..35f1136
--- /dev/null
+++ b/lib/screens/fooddetails/food_details.dart
@@ -0,0 +1,220 @@
+import 'package:flutter/material.dart';
+import 'package:spoonshare/widgets/bottom_navbar.dart';
+import 'package:url_launcher/url_launcher.dart';
+import 'package:intl/intl.dart';
+
+class FoodDetailsScreen extends StatelessWidget {
+ final Map data;
+
+ const FoodDetailsScreen({required this.data, Key? key}) : super(key: key);
+
+// Function to launch Google Maps with Directions
+ Future _launchMaps(String location) async {
+ final Uri uri = Uri.parse(
+ 'https://www.google.com/maps/dir/?api=1&destination=$location',
+ );
+ await launchUrl(uri);
+ }
+
+ String _formatDate(String date) {
+ final inputFormat = DateFormat('yyyy-MM-dd');
+ final outputFormat = DateFormat('dd-MM-yyyy');
+ final formattedDate = outputFormat.format(inputFormat.parse(date));
+ return formattedDate;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: SingleChildScrollView(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const SizedBox(height: 20),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ IconButton(
+ icon: const Icon(Icons.arrow_back),
+ onPressed: () {
+ Navigator.pop(context);
+ },
+ ),
+ Text(
+ 'Food Details: ${data['venue'] ?? ''}',
+ style: const TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ const SizedBox(
+ width: 42, height: 42),
+ ],
+ ),
+ const SizedBox(height: 20),
+ Container(
+ width: 360,
+ decoration: ShapeDecoration(
+ shape: RoundedRectangleBorder(
+ side: BorderSide(
+ width: 2,
+ color: Colors.black.withOpacity(0.1),
+ ),
+ borderRadius: BorderRadius.circular(15),
+ ),
+ ),
+ ),
+ const SizedBox(height: 20),
+ Container(
+ width: double.infinity,
+ decoration: ShapeDecoration(
+ shape: RoundedRectangleBorder(
+ side: BorderSide(
+ width: 2,
+ color: Colors.black.withOpacity(0.1),
+ ),
+ borderRadius: BorderRadius.circular(15),
+ ),
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ ClipRRect(
+ borderRadius: BorderRadius.circular(12),
+ child: Container(
+ padding: const EdgeInsets.all(0),
+ child: Image.network(
+ data['imageUrl'] ?? '',
+ height: 200,
+ width: double.infinity,
+ ),
+ ),
+ ),
+ Padding(
+ padding:
+ const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
+ child: Text(
+ 'Venue: ${data['venue'] ?? ''}',
+ style: const TextStyle(
+ fontSize: 24,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ),
+ Padding(
+ padding:
+ const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
+ child: Text(
+ 'Uploaded By: ${data['fullName'] ?? ''}',
+ style: const TextStyle(fontSize: 16),
+ ),
+ ),
+ Padding(
+ padding:
+ const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
+ child: Text(
+ 'Location: ${data['address'] ?? ''}',
+ style: const TextStyle(fontSize: 16),
+ ),
+ ),
+ Padding(
+ padding:
+ const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
+ child: Text(
+ 'Community: ${data['community'] ?? ''}',
+ style: const TextStyle(fontSize: 16),
+ ),
+ ),
+ Padding(
+ padding:
+ const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
+ child: Text(
+ 'Food Type: ${data['foodType'] ?? ''}',
+ style: const TextStyle(fontSize: 16),
+ ),
+ ),
+ Padding(
+ padding:
+ const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
+ child: Table(
+ defaultVerticalAlignment:
+ TableCellVerticalAlignment.middle,
+ border: TableBorder.all(
+ color: Colors.grey,
+ width: 1.0,
+ ),
+ children: [
+ TableRow(
+ children: [
+ TableCell(
+ child: _buildCell('From'),
+ ),
+ TableCell(
+ child: _buildCell('To'),
+ ),
+ ],
+ ),
+ TableRow(
+ children: [
+ TableCell(
+ child: _buildCell(
+ _formatDate('${data['date'] ?? ''}')),
+ ),
+ TableCell(
+ child: _buildCell(
+ _formatDate('${data['toDate'] ?? ''}')),
+ ),
+ ],
+ ),
+ TableRow(
+ children: [
+ TableCell(
+ child: _buildCell('${data['time'] ?? ''}'),
+ ),
+ TableCell(
+ child: _buildCell('${data['toTime'] ?? ''}'),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.all(12),
+ child: ElevatedButton(
+ onPressed: () =>
+ _launchMaps(data['venue'] + data['address'] ),
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Colors.black,
+ foregroundColor: Colors.white,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(30),
+ ),
+ ),
+ child: const Text('Get Directions'),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ bottomNavigationBar: const BottomNavBar(),
+ );
+ }
+
+ Widget _buildCell(String text) {
+ return Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Text(
+ text,
+ style: const TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/screens/home/home.dart b/lib/screens/home/home.dart
new file mode 100644
index 0000000..61037ee
--- /dev/null
+++ b/lib/screens/home/home.dart
@@ -0,0 +1,59 @@
+import 'package:flutter/material.dart';
+import 'package:spoonshare/screens/home/home_page.dart';
+import 'package:spoonshare/models/users/user.dart';
+import 'package:spoonshare/screens/onboarding.dart';
+import 'package:spoonshare/widgets/random_quotes.dart';
+
+class HomeScreen extends StatelessWidget {
+ const HomeScreen({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ UserProfile userProfile = UserProfile();
+
+ return FutureBuilder(
+ future: userProfile.loadUserProfile(),
+ builder: (context, snapshot) {
+ if (snapshot.connectionState == ConnectionState.done) {
+ if (!userProfile.isAuthenticated()) {
+ Future.delayed(Duration.zero, () {
+ Navigator.pushReplacement(
+ context,
+ MaterialPageRoute(builder: (context) => const Onboarding()),
+ );
+ });
+ }
+
+ String name = userProfile.getFullName();
+ String role = userProfile.getRole();
+
+ return userProfile.isAuthenticated()
+ ? Scaffold(
+ body: Center(
+ child: HomePage(name: name, role: role),
+ ),
+ )
+ : Container();
+ } else {
+ return Scaffold(
+ body: Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Image.asset('assets/images/spoonshare_launcher.png',
+ width: 150, height: 150),
+ const SizedBox(height: 20),
+ Text(
+ 'Food Sharing Quote: ${RandomQuotes.getRandomFoodSharingQuote()}',
+ style: const TextStyle(fontStyle: FontStyle.italic),
+ textAlign: TextAlign.center,
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+ },
+ );
+ }
+}
diff --git a/lib/screens/home/home_page.dart b/lib/screens/home/home_page.dart
new file mode 100644
index 0000000..9448579
--- /dev/null
+++ b/lib/screens/home/home_page.dart
@@ -0,0 +1,301 @@
+import 'package:flutter/material.dart';
+import 'package:spoonshare/screens/donate/donate_page.dart';
+import 'package:spoonshare/screens/volunteer/volunteer_screen.dart';
+import 'package:spoonshare/widgets/bottom_navbar.dart';
+import 'package:spoonshare/widgets/nearby_food_cards.dart';
+
+class HomePage extends StatefulWidget {
+ const HomePage({Key? key, required this.name, required this.role})
+ : super(key: key);
+ final String name;
+ final String role;
+
+ @override
+ _HomePageState createState() => _HomePageState();
+}
+
+class _HomePageState extends State {
+ int currentQuoteIndex = 0;
+ List quotes = [
+ 'Share a meal, spread joy, and make a difference today.',
+ 'Nourishing hearts with kindness, one shared plate at a time.',
+ 'In the banquet of life, everyone deserves a seat and feast.',
+ ];
+
+ @override
+ void initState() {
+ super.initState();
+
+ _startChangingQuotes();
+ }
+
+ void _startChangingQuotes() {
+ Future.delayed(const Duration(seconds: 5), () {
+ if (mounted) {
+ setState(() {
+ currentQuoteIndex = (currentQuoteIndex + 1) % quotes.length;
+ });
+ _startChangingQuotes();
+ }
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: SingleChildScrollView(
+ child: Padding(
+ padding: const EdgeInsets.all(18.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const SizedBox(height: 30),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ 'Hi ${widget.name}',
+ style: const TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ Text(
+ widget.role,
+ style: const TextStyle(
+ fontSize: 16,
+ color: Colors.grey,
+ ),
+ ),
+ ],
+ ),
+ 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,
+ height: 42,
+ decoration: ShapeDecoration(
+ color: Colors.black.withOpacity(0.08),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(50),
+ ),
+ ),
+ child: IconButton(
+ icon: const Icon(Icons.notifications),
+ onPressed: () {},
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ const SizedBox(height: 20),
+ Container(
+ width: 360,
+ decoration: ShapeDecoration(
+ shape: RoundedRectangleBorder(
+ side: BorderSide(
+ width: 2,
+ color: Colors.black.withOpacity(0.1),
+ ),
+ borderRadius:
+ BorderRadius.circular(15), // Added border radius
+ ),
+ ),
+ ),
+ const SizedBox(height: 20),
+ Center(
+ child: SizedBox(
+ width: 208,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Text.rich(
+ TextSpan(
+ children: [
+ const TextSpan(
+ text: '“',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 16,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w500,
+ height: 0,
+ ),
+ ),
+ TextSpan(
+ text: quotes[currentQuoteIndex],
+ style: TextStyle(
+ color: Colors.black.withOpacity(0.8),
+ fontSize: 16,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w500,
+ height: 0,
+ ),
+ ),
+ const TextSpan(
+ text: '”',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 16,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w500,
+ height: 0,
+ ),
+ ),
+ ],
+ ),
+ textAlign: TextAlign.center,
+ ),
+ const SizedBox(height: 20),
+ SizedBox(
+ width: double.infinity,
+ height: 100,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Container(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 16, vertical: 8),
+ decoration: ShapeDecoration(
+ color: const Color(0xFF009E48),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(50),
+ ),
+ ),
+ child: GestureDetector(
+ onTap: () {
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) => const DonatePage(),
+ ),
+ );
+ },
+ child: const Row(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Text(
+ 'Donate Food',
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 14,
+ fontFamily: 'Inter',
+ fontWeight: FontWeight.w700,
+ height: 0,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ const SizedBox(height: 6),
+ Text(
+ 'OR',
+ style: TextStyle(
+ color: Colors.black.withOpacity(0.4),
+ fontSize: 12,
+ fontFamily: 'Inter',
+ fontWeight: FontWeight.w700,
+ height: 0,
+ ),
+ ),
+ const SizedBox(height: 8),
+ Container(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 16, vertical: 8),
+ decoration: ShapeDecoration(
+ color: Colors.black,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(50),
+ ),
+ ),
+ child: GestureDetector(
+ onTap: () {
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) =>
+ const VolunteerScreen(),
+ ),
+ );
+ },
+ child: const Row(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Text(
+ 'Become Volunteer',
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 14,
+ fontFamily: 'Inter',
+ fontWeight: FontWeight.w700,
+ height: 0,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ Container(
+ margin: const EdgeInsets.only(top: 30),
+ alignment: Alignment.centerLeft,
+ child: const Text(
+ 'Near By Available Foods',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 16,
+ fontFamily: 'Inter',
+ fontWeight: FontWeight.w700,
+ height: 0,
+ ),
+ ),
+ ),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const SizedBox(height: 5),
+ NearbyFoodCard(),
+ const SizedBox(height: 5),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ bottomNavigationBar: const BottomNavBar(),
+ );
+ }
+}
diff --git a/lib/screens/jobs/job_page.dart b/lib/screens/jobs/job_page.dart
new file mode 100644
index 0000000..7822e2d
--- /dev/null
+++ b/lib/screens/jobs/job_page.dart
@@ -0,0 +1,16 @@
+import 'package:flutter/material.dart';
+import 'package:spoonshare/widgets/bottom_navbar.dart';
+
+class JobPage extends StatelessWidget {
+ const JobPage({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return const Scaffold(
+ body: Center(
+ child: Text("Job Page"),
+ ),
+ bottomNavigationBar: BottomNavBar(),
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/screens/onboarding.dart b/lib/screens/onboarding.dart
new file mode 100644
index 0000000..ea66e35
--- /dev/null
+++ b/lib/screens/onboarding.dart
@@ -0,0 +1,154 @@
+import 'package:flutter/material.dart';
+import 'package:spoonshare/screens/auth/signup.dart';
+
+class Onboarding extends StatelessWidget {
+ const Onboarding({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ final double screenWidth = MediaQuery.of(context).size.width;
+ final double screenHeight = MediaQuery.of(context).size.height;
+
+ return Scaffold(
+ body: Center(
+ child: Container(
+ width: screenWidth,
+ height: screenHeight,
+ clipBehavior: Clip.antiAlias,
+ decoration: const BoxDecoration(color: Colors.white),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Container(
+ width: screenWidth * 0.4667,
+ height: screenHeight * 0.04,
+ margin: const EdgeInsets.only(top: 12),
+ padding: const EdgeInsets.symmetric(
+ horizontal: 16,
+ vertical: 8,
+ ),
+ decoration: BoxDecoration(
+ color: Colors.black.withOpacity(0.86),
+ borderRadius: BorderRadius.circular(50),
+ ),
+ child: const Center(
+ child: Text(
+ 'MEALS OF GRACE BY',
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 15,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w700,
+ height: 0,
+ ),
+ ),
+ ),
+ ),
+ const Text(
+ 'SpoonShare',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 42,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w800,
+ ),
+ ),
+ const SizedBox(height: 8),
+ const Text(
+ 'अब भूखे नहीं रहेंगे.',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 20,
+ fontFamily: 'Asar',
+ fontWeight: FontWeight.w400,
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(height: 20),
+ const Center(
+ child: SizedBox(
+ width: 280,
+ child: Text.rich(
+ TextSpan(
+ text: 'Don’t throw,\nsend it to us.',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 32,
+ fontStyle: FontStyle.italic,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w700,
+ height: 0,
+ ),
+ ),
+ textAlign: TextAlign.center,
+ ),
+ ),
+ ),
+ const SizedBox(height: 32),
+ Container(
+ width: screenWidth * 0.875,
+ height: screenHeight * 0.39,
+ decoration: const BoxDecoration(
+ image: DecorationImage(
+ image: AssetImage("assets/images/onboarding.png"),
+ fit: BoxFit.fill,
+ ),
+ ),
+ ),
+ SizedBox(
+ width: 280,
+ child: Text(
+ 'Don’t throw your food, send it to us we will give it to some in need and don’t have enough money to buy themself.',
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ color: Colors.black.withOpacity(0.7799999713897705),
+ fontSize: 14,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w400,
+ height: 0,
+ ),
+ ),
+ ),
+ const SizedBox(height: 32),
+ Container(
+ width: screenWidth * 0.8667,
+ height: screenHeight * 0.05625,
+ margin: const EdgeInsets.only(top: 20),
+ decoration: BoxDecoration(
+ color: Colors.black,
+ borderRadius: BorderRadius.circular(50),
+ ),
+ child: InkWell(
+ onTap: () {
+ // Navigate to another screen
+ Navigator.push(
+ context,
+ MaterialPageRoute(builder: (context) => SignUpScreen()),
+ );
+ },
+ child: const Center(
+ child: Text(
+ 'Get Started',
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 18,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w500,
+ letterSpacing: 0.36,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/screens/profile/user_profile.dart b/lib/screens/profile/user_profile.dart
new file mode 100644
index 0000000..cc74292
--- /dev/null
+++ b/lib/screens/profile/user_profile.dart
@@ -0,0 +1,637 @@
+// ignore_for_file: use_build_context_synchronously
+import 'package:cloud_firestore/cloud_firestore.dart';
+import 'package:firebase_auth/firebase_auth.dart';
+import 'package:firebase_storage/firebase_storage.dart';
+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/volunteer/volunteer_screen.dart';
+import 'package:spoonshare/services/auth.dart';
+import 'package:spoonshare/widgets/bottom_navbar.dart';
+import 'package:image_picker/image_picker.dart';
+import 'package:spoonshare/widgets/loader.dart';
+import 'package:spoonshare/widgets/snackbar.dart';
+import 'package:device_info_plus/device_info_plus.dart';
+import 'dart:io';
+
+class UserProfileScreen extends StatefulWidget {
+ const UserProfileScreen({
+ Key? key,
+ required this.name,
+ required this.role,
+ }) : super(key: key);
+
+ final String name;
+ final String role;
+
+ @override
+ _UserProfileScreenState createState() => _UserProfileScreenState();
+}
+
+class _UserProfileScreenState extends State {
+ bool isEditing = false;
+ AuthService authService = AuthService();
+
+ late TextEditingController nameController;
+ late TextEditingController emailController;
+ late TextEditingController contactController;
+ late TextEditingController orgController;
+ late TextEditingController roleController;
+ late TextEditingController profileImageController;
+
+ @override
+ void initState() {
+ super.initState();
+ nameController = TextEditingController();
+ emailController = TextEditingController();
+ contactController = TextEditingController();
+ orgController = TextEditingController();
+ roleController = TextEditingController();
+ profileImageController = TextEditingController();
+ loadUserProfileData();
+ }
+
+ Future loadUserProfileData() async {
+ UserProfile userProfile = UserProfile();
+ await userProfile.loadUserProfile();
+
+ setState(() {
+ nameController.text = widget.name;
+ emailController.text = userProfile.getEmail();
+ contactController.text = userProfile.getContactNumber();
+ orgController.text = userProfile.getOrganisation();
+ roleController.text = userProfile.getRole();
+ profileImageController.text = userProfile.getProfileImageUrl();
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: SingleChildScrollView(
+ child: Padding(
+ padding: const EdgeInsets.all(18.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const SizedBox(height: 30),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ 'Hi ${widget.name}',
+ style: const TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ Text(
+ widget.role,
+ style: const TextStyle(
+ fontSize: 16,
+ color: Colors.grey,
+ ),
+ ),
+ ],
+ ),
+ 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.logout),
+ onPressed: () {
+ authService.signOut(context);
+ },
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ const SizedBox(height: 8),
+ Container(
+ width: 360,
+ decoration: ShapeDecoration(
+ shape: RoundedRectangleBorder(
+ side: BorderSide(
+ width: 2,
+ color: Colors.black.withOpacity(0.1),
+ ),
+ borderRadius: BorderRadius.circular(15),
+ ),
+ ),
+ ),
+ const SizedBox(height: 8),
+ Center(
+ child: SizedBox(
+ width: 312,
+ child: Column(
+ crossAxisAlignment:
+ CrossAxisAlignment.center, // Center content
+ children: [
+ const SizedBox(height: 20),
+ Stack(
+ alignment: Alignment.center,
+ children: [
+ Container(
+ width: 160,
+ height: 160,
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ border: Border.all(
+ width: 2,
+ color: Colors.black.withOpacity(0.1),
+ ),
+ ),
+ child: CircleAvatar(
+ radius: 80,
+ backgroundImage: profileImageController
+ .text.isNotEmpty
+ ? NetworkImage(profileImageController.text)
+ : const NetworkImage(
+ "https://www.shutterstock.com/image-vector/vector-flat-illustration-grayscale-avatar-600nw-2264922221.jpg",
+ ),
+ ),
+ ),
+ Positioned(
+ bottom: 0,
+ right: 0,
+ child: Container(
+ padding: const EdgeInsets.all(6),
+ decoration: const BoxDecoration(
+ shape: BoxShape.circle,
+ color: Colors.white,
+ ),
+ child: IconButton(
+ icon: const Icon(
+ Icons.camera_alt,
+ color: Colors.black,
+ ),
+ onPressed: () async {
+ await pickFile();
+ }),
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(
+ height: 30,
+ ),
+ Container(
+ padding: const EdgeInsets.all(12),
+ width: 325,
+ decoration: BoxDecoration(
+ border: Border.all(
+ width: 1,
+ color: Colors.black.withOpacity(0.2),
+ ),
+ borderRadius: BorderRadius.circular(20),
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ children: [
+ const Icon(Icons.info_outline),
+ const SizedBox(width: 8),
+ const Text(
+ 'Personal Information',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 16,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w700,
+ ),
+ ),
+ const Spacer(),
+ isEditing
+ ? Row(
+ children: [
+ IconButton(
+ icon: const Icon(Icons.cancel),
+ onPressed: () {
+ setState(() {
+ isEditing = false;
+ });
+ },
+ ),
+ IconButton(
+ icon: const Icon(Icons.check),
+ onPressed: () {
+ setState(() {
+ isEditing = false;
+ });
+ },
+ ),
+ ],
+ )
+ : IconButton(
+ icon: const Icon(Icons.edit),
+ onPressed: () {
+ setState(() {
+ isEditing = true;
+ });
+ },
+ ),
+ ],
+ ),
+ const SizedBox(height: 16),
+ Container(
+ width: double.infinity,
+ height: 1,
+ color: Colors.black.withOpacity(0.2),
+ ),
+ const SizedBox(height: 16),
+ buildEditableField(
+ 'Name / नाम',
+ nameController,
+ TextInputType.text,
+ ),
+ const SizedBox(height: 16),
+ buildEditableField(
+ 'Email / ईमेल',
+ emailController,
+ TextInputType.emailAddress,
+ ),
+ const SizedBox(height: 16),
+ buildEditableField(
+ 'Contact / संपर्क',
+ contactController,
+ TextInputType.phone,
+ ),
+ const SizedBox(height: 16),
+ if (isEditing)
+ ElevatedButton(
+ onPressed: () async {
+ setState(() {
+ isEditing = false;
+ });
+ await updateUserProfile();
+ },
+ style: ElevatedButton.styleFrom(
+ foregroundColor: Colors.white,
+ backgroundColor: Colors.black,
+ ),
+ child: const Text('Submit'),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(
+ height: 30,
+ ),
+ Container(
+ padding: const EdgeInsets.all(12),
+ width: 325,
+ decoration: BoxDecoration(
+ border: Border.all(
+ width: 1,
+ color: Colors.black.withOpacity(0.2),
+ ),
+ borderRadius: BorderRadius.circular(20),
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const Row(
+ children: [
+ Icon(Icons.info_outline),
+ SizedBox(width: 8),
+ Text(
+ 'Organisation Information',
+ style: TextStyle(
+ color: Colors.black,
+ fontSize: 14,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w700,
+ ),
+ ),
+ /* const Spacer(),
+ isEditing
+ ? Row(
+ children: [
+ IconButton(
+ icon: const Icon(Icons.cancel),
+ onPressed: () {
+ setState(() {
+ isEditing = false;
+ });
+ },
+ ),
+ IconButton(
+ icon: const Icon(Icons.check),
+ onPressed: () {
+ setState(() {
+ isEditing = false;
+ });
+ },
+ ),
+ ],
+ )
+ : IconButton(
+ icon: const Icon(Icons.edit),
+ onPressed: () {
+ setState(() {
+ isEditing = true;
+ });
+ },
+ ),
+ */
+ ],
+ ),
+ const SizedBox(height: 10),
+ Container(
+ width: double.infinity,
+ height: 1,
+ color: Colors.black.withOpacity(0.2),
+ ),
+ const SizedBox(height: 16),
+ buildEditableField(
+ 'Organisation',
+ orgController,
+ TextInputType.text,
+ ),
+ const SizedBox(height: 16),
+ buildEditableField(
+ 'Role',
+ roleController,
+ TextInputType.text,
+ ),
+ const SizedBox(height: 16),
+ if (isEditing)
+ ElevatedButton(
+ onPressed: () async {
+ setState(() {
+ isEditing = false;
+ });
+ },
+ style: ElevatedButton.styleFrom(
+ foregroundColor: Colors.white,
+ backgroundColor: Colors.black,
+ ),
+ child: const Text('Submit'),
+ ),
+ const SizedBox(height: 16),
+ ElevatedButton(
+ onPressed: () {
+ Navigator.pushReplacement(
+ context,
+ MaterialPageRoute(
+ builder: (context) =>
+ const VolunteerScreen(),
+ ),
+ );
+ },
+ style: ElevatedButton.styleFrom(
+ foregroundColor: Colors.white,
+ backgroundColor: Colors.black,
+ ),
+ child: const Text('Become A Volunteer'),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ bottomNavigationBar: const BottomNavBar(),
+ );
+ }
+
+ Widget buildEditableField(
+ String label,
+ TextEditingController controller,
+ TextInputType keyboardType,
+ ) {
+ return isEditing
+ ? Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ label,
+ style: TextStyle(
+ color: Colors.black.withOpacity(0.4),
+ fontSize: 14,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ TextFormField(
+ controller: controller,
+ keyboardType: keyboardType,
+ style: const TextStyle(
+ color: Colors.black,
+ fontSize: 16,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w700,
+ ),
+ ),
+ ],
+ )
+ : Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ label,
+ style: TextStyle(
+ color: Colors.black.withOpacity(0.4),
+ fontSize: 14,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ Text(
+ controller.text,
+ style: const TextStyle(
+ color: Colors.black,
+ fontSize: 16,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w700,
+ ),
+ ),
+ ],
+ );
+ }
+
+ Future pickFile() async {
+ final androidInfo = await DeviceInfoPlugin().androidInfo;
+ late final Map status;
+ if (androidInfo.version.sdkInt <= 32) {
+ status = await [
+ Permission.storage,
+ ].request();
+ } else {
+ status = await [Permission.photos, Permission.notification].request();
+ }
+
+ var allAccepted = true;
+ status.forEach((permission, status) {
+ if (status != PermissionStatus.granted) {
+ allAccepted = false;
+ }
+ });
+
+ if (allAccepted) {
+ XFile? pickedImage = await ImagePicker().pickImage(
+ source: ImageSource.gallery,
+ );
+
+ if (pickedImage != null) {
+ File imageFile = File(pickedImage.path);
+
+ // Assuming you have a function to get the current user ID
+ String userId = FirebaseAuth.instance.currentUser!.uid;
+
+ // Upload the image to Firebase Storage
+ String imageUrl = await uploadImageToFirebaseStorage(imageFile, userId);
+
+ // Update the profile image URL
+ updateProfileImage(imageUrl);
+
+ showSuccessSnackbar(context, "Profile Image Upload successfully!");
+ }
+ } else {
+ showErrorSnackbar(context, 'Storage permission denied');
+ print('Storage permission denied');
+ }
+ }
+
+ Future uploadImageToFirebaseStorage(
+ File imageFile, String userId) async {
+ try {
+ showLoadingDialog(context);
+
+ String fileName = 'profile_$userId.jpg';
+ Reference storageReference =
+ FirebaseStorage.instance.ref().child('users/profileImages/$fileName');
+
+ await storageReference.putFile(imageFile);
+
+ String imageUrl = await storageReference.getDownloadURL();
+ return imageUrl;
+ } catch (e) {
+ print('Error uploading image to Firebase Storage: $e');
+ return '';
+ } finally {
+ Navigator.pop(context);
+ }
+ }
+
+ Future updateProfileImage(String imageUrl) async {
+ UserProfile userProfile = UserProfile();
+
+ try {
+ showLoadingDialog(context);
+
+ setState(() {
+ userProfile.profileImageUrl = imageUrl;
+ profileImageController.text = imageUrl;
+ });
+
+ // Update in SharedPreferences
+ SharedPreferences prefs = await SharedPreferences.getInstance();
+ prefs.setString('profileImageUrl', imageUrl);
+
+ // Assuming you have a Firestore collection named 'users'
+ CollectionReference users =
+ FirebaseFirestore.instance.collection('users');
+
+ String userId = FirebaseAuth.instance.currentUser!.uid;
+
+ // Perform the update
+ await users.doc(userId).update({
+ 'profileImageUrl': imageUrl,
+ });
+
+ showSuccessSnackbar(context, "Profile image updated successfully!");
+
+ print("Profile image updated successfully!");
+ } catch (e) {
+ // Handle the exception
+ showErrorSnackbar(context, "Error updating profile image: $e");
+ print("Error updating profile image: $e");
+ } finally {
+ Navigator.pop(context);
+ }
+ }
+
+ Future updateUserProfile() async {
+ UserProfile userProfile = UserProfile();
+ showLoadingDialog(context); // Show loader
+ try {
+ // Update locally
+ setState(() {
+ userProfile.fullName = nameController.text;
+ userProfile.email = emailController.text;
+ userProfile.contactNumber = contactController.text;
+ });
+
+ // Update in SharedPreferences
+ SharedPreferences prefs = await SharedPreferences.getInstance();
+ prefs.setString('fullName', nameController.text);
+ prefs.setString('email', emailController.text);
+ prefs.setString('contactNumber', contactController.text);
+
+ // Assuming you have a Firestore collection named 'users'
+ CollectionReference users =
+ FirebaseFirestore.instance.collection('users');
+
+ String userId = FirebaseAuth.instance.currentUser!.uid;
+
+ // Perform the update
+ await users.doc(userId).update({
+ 'fullName': nameController.text,
+ 'email': emailController.text,
+ 'contactNumber': contactController.text,
+ });
+
+ print("Updated successfully!");
+ } catch (e) {
+ print("Error updating user profile: $e");
+ } finally {
+ Navigator.pop(context); // Dismiss loader
+ }
+ }
+
+/* Future updateOrganisationAndRole() async {
+ UserProfile userProfile = UserProfile();
+
+ setState(() {
+ userProfile.organisation = orgController.text;
+ userProfile.role = roleController.text;
+ });
+
+ SharedPreferences prefs = await SharedPreferences.getInstance();
+ prefs.setString('organisation', orgController.text);
+ prefs.setString('role', roleController.text);
+
+ CollectionReference users = FirebaseFirestore.instance.collection('users');
+
+ String userId = FirebaseAuth.instance.currentUser!.uid;
+
+ await users.doc(userId).update({
+ 'organisation': orgController.text,
+ 'role': roleController.text,
+ });
+
+ print("Organisation and role updated successfully!");
+ }*/
+}
diff --git a/lib/screens/volunteer/volunteer_screen.dart b/lib/screens/volunteer/volunteer_screen.dart
new file mode 100644
index 0000000..6aad87c
--- /dev/null
+++ b/lib/screens/volunteer/volunteer_screen.dart
@@ -0,0 +1,16 @@
+import 'package:flutter/material.dart';
+import 'package:spoonshare/widgets/bottom_navbar.dart';
+
+class VolunteerScreen extends StatelessWidget {
+ const VolunteerScreen({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return const Scaffold(
+ body: Center(
+ child: Text('Volunteer Screen'),
+ ),
+ bottomNavigationBar: BottomNavBar(),
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/services/auth.dart b/lib/services/auth.dart
new file mode 100644
index 0000000..44ee0a2
--- /dev/null
+++ b/lib/services/auth.dart
@@ -0,0 +1,40 @@
+// ignore_for_file: use_build_context_synchronously
+
+import 'package:firebase_auth/firebase_auth.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+import 'package:spoonshare/screens/auth/signin.dart';
+import 'package:spoonshare/widgets/snackbar.dart';
+import 'package:flutter/material.dart';
+
+class AuthService {
+ final FirebaseAuth _auth = FirebaseAuth.instance;
+
+ // Check if the user is authenticated
+ Future isAuthenticated() async {
+ return _auth.currentUser != null;
+ }
+
+ // Sign out the user
+ Future signOut(BuildContext context) async {
+ try {
+ await _clearLocalUserData();
+ await _auth.signOut();
+
+ showSuccessSnackbar(context, "Sign Out Successful");
+
+ Navigator.push(
+ context,
+ MaterialPageRoute(builder: (context) => const SignInScreen()),
+ );
+ } catch (e) {
+ print('Error during logout: $e');
+ showErrorSnackbar(context, "Sign Out Failed");
+ }
+ }
+
+ // Clear local user data from shared preferences
+ Future _clearLocalUserData() async {
+ SharedPreferences prefs = await SharedPreferences.getInstance();
+ prefs.clear();
+ }
+}
diff --git a/lib/services/forgot_password.dart b/lib/services/forgot_password.dart
new file mode 100644
index 0000000..bf3f0c0
--- /dev/null
+++ b/lib/services/forgot_password.dart
@@ -0,0 +1,13 @@
+import 'package:firebase_auth/firebase_auth.dart';
+
+class ForgotPasswordService {
+ final FirebaseAuth _auth = FirebaseAuth.instance;
+
+ Future resetPassword(String email) async {
+ try {
+ await _auth.sendPasswordResetEmail(email: email);
+ } catch (e) {
+ throw e;
+ }
+ }
+}
diff --git a/lib/splash_screen.dart b/lib/splash_screen.dart
new file mode 100644
index 0000000..64c625b
--- /dev/null
+++ b/lib/splash_screen.dart
@@ -0,0 +1,43 @@
+import 'dart:async';
+import 'package:flutter/material.dart';
+import 'package:spoonshare/screens/home/home.dart';
+
+class SplashScreen extends StatefulWidget {
+ const SplashScreen({Key? key}) : super(key: key);
+
+ @override
+ _SplashScreenState createState() => _SplashScreenState();
+}
+
+class _SplashScreenState extends State {
+ @override
+ void initState() {
+ super.initState();
+ Timer(const Duration(seconds: 3), () {
+ Navigator.of(context).pushReplacement(
+ MaterialPageRoute(
+ builder: (context) =>
+ const HomeScreen(),
+ )
+ );
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Image.asset('assets/images/spoonshare_launcher.png', width: 150, height: 150),
+ const SizedBox(height: 20),
+ const CircularProgressIndicator(
+ color: Colors.black45,
+ )
+ ],
+ ),
+ ),
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/widgets/bottom_navbar.dart b/lib/widgets/bottom_navbar.dart
new file mode 100644
index 0000000..7ec7b95
--- /dev/null
+++ b/lib/widgets/bottom_navbar.dart
@@ -0,0 +1,111 @@
+import 'package:flutter/material.dart';
+import 'package:spoonshare/models/users/user.dart';
+import 'package:spoonshare/screens/chat/chat_page.dart';
+import 'package:spoonshare/screens/donate/donate_page.dart';
+import 'package:spoonshare/screens/home/home_page.dart';
+import 'package:spoonshare/screens/jobs/job_page.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: double.infinity,
+ 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', Colors.black54,
+ Colors.black54, HomePage(name: name, role: role)),
+ _buildNavItem(context, Icons.chat, 'Chat', Colors.black54,
+ Colors.black54, const ChatPage()),
+ _buildNavItem(context, Icons.add_circle, 'Donate Food',
+ Colors.black54, Colors.black54, const DonatePage()),
+ _buildNavItem(context, Icons.work, 'Jobs', Colors.black54,
+ Colors.black54, const JobPage()),
+ _buildNavItem(
+ context,
+ Icons.person,
+ 'Profile',
+ Colors.black54,
+ 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: Colors.black,
+ ),
+ const SizedBox(height: 2),
+ Text(
+ label,
+ style: const TextStyle(
+ color: Colors.black,
+ fontSize: 12,
+ fontFamily: 'Roboto',
+ fontWeight: FontWeight.w700,
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/widgets/custom_text_field.dart b/lib/widgets/custom_text_field.dart
new file mode 100644
index 0000000..40f44d0
--- /dev/null
+++ b/lib/widgets/custom_text_field.dart
@@ -0,0 +1,33 @@
+import 'package:flutter/material.dart';
+
+class CustomTextField extends StatelessWidget {
+ final String label;
+ final TextEditingController controller;
+
+ const CustomTextField({
+ Key? key,
+ required this.label,
+ required this.controller,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return TextField(
+ controller: controller,
+ decoration: InputDecoration(
+ labelText: label,
+ border: const OutlineInputBorder(
+ borderSide: BorderSide(
+ color: Colors.black,
+ ),
+ ),
+ focusedBorder: const OutlineInputBorder(
+ borderSide: BorderSide(
+ color: Colors.black,
+ ),
+ ),
+ labelStyle: const TextStyle(color: Colors.black),
+ ),
+ );
+ }
+}
diff --git a/lib/widgets/loader.dart b/lib/widgets/loader.dart
new file mode 100644
index 0000000..8c4681f
--- /dev/null
+++ b/lib/widgets/loader.dart
@@ -0,0 +1,11 @@
+import 'package:flutter/material.dart';
+
+void showLoadingDialog(BuildContext context) {
+ showDialog(
+ context: context,
+ barrierDismissible: false, // Prevent dismissing by tapping outside
+ builder: (context) => const Center(
+ child: CircularProgressIndicator(),
+ ),
+ );
+}
diff --git a/lib/widgets/nearby_food_cards.dart b/lib/widgets/nearby_food_cards.dart
new file mode 100644
index 0000000..d6940ae
--- /dev/null
+++ b/lib/widgets/nearby_food_cards.dart
@@ -0,0 +1,117 @@
+import 'package:cloud_firestore/cloud_firestore.dart';
+import 'package:flutter/material.dart';
+import 'package:spoonshare/screens/fooddetails/food_details.dart';
+
+class NearbyFoodCard extends StatelessWidget {
+ const NearbyFoodCard({super.key});
+
+ void _navigateToFoodDetails(BuildContext context, Map data) {
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) => FoodDetailsScreen(data: data),
+ ),
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return StreamBuilder>>(
+ stream: FirebaseFirestore.instance
+ .collection('food')
+ .doc('sharedfood')
+ .collection('foodData')
+ .orderBy('timestamp', descending: true)
+ .snapshots(),
+ builder: (context, snapshot) {
+ if (snapshot.hasError) {
+ return Text('Error: ${snapshot.error}');
+ }
+
+ List>> foodDocs =
+ snapshot.data?.docs ?? [];
+
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const SizedBox(height: 5),
+ ...(foodDocs.isEmpty
+ ? [const Text('No data available.')]
+ : foodDocs.map((doc) {
+ Map data = doc.data();
+ return GestureDetector(
+ onTap: () => _navigateToFoodDetails(context, data),
+ child: Card(
+ margin: const EdgeInsets.all(8),
+ elevation: 8,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(10),
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [
+ ClipRRect(
+ borderRadius: const BorderRadius.vertical(
+ top: Radius.circular(10)),
+ child: Container(
+ decoration: BoxDecoration(
+ boxShadow: [
+ BoxShadow(
+ color: Colors.black.withOpacity(0.2),
+ spreadRadius: 1,
+ blurRadius: 5,
+ offset: const Offset(0, 3),
+ ),
+ ],
+ ),
+ child: Image.network(
+ data['imageUrl'] ?? '',
+ height: 200,
+ fit: BoxFit.cover,
+ ),
+ ),
+ ),
+ Container(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ data['venue'] ?? '',
+ style: const TextStyle(
+ color: Colors.black,
+ fontSize: 20,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ const SizedBox(height: 4),
+ Text(
+ 'Uploaded By: ${data['fullName'] ?? ''}',
+ style: const TextStyle(
+ color: Colors.black87,
+ fontSize: 14,
+ ),
+ ),
+ const SizedBox(height: 4),
+ Text(
+ 'Location: ${data['address'] ?? ''}',
+ style: const TextStyle(
+ color: Colors.black87,
+ fontSize: 14,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ })),
+ const SizedBox(height: 5),
+ ],
+ );
+ },
+ );
+ }
+}
diff --git a/lib/widgets/random_quotes.dart b/lib/widgets/random_quotes.dart
new file mode 100644
index 0000000..0c01f82
--- /dev/null
+++ b/lib/widgets/random_quotes.dart
@@ -0,0 +1,15 @@
+import 'dart:math';
+
+class RandomQuotes {
+ static final List foodSharingQuotes = [
+ 'Sharing food is sharing love.',
+ 'A shared meal is a shared joy.',
+ 'Food is better when shared with friends.',
+ 'The more you share, the more you have.',
+ ];
+
+ static String getRandomFoodSharingQuote() {
+ final Random random = Random();
+ return foodSharingQuotes[random.nextInt(foodSharingQuotes.length)];
+ }
+}
diff --git a/lib/widgets/snackbar.dart b/lib/widgets/snackbar.dart
new file mode 100644
index 0000000..be12bc6
--- /dev/null
+++ b/lib/widgets/snackbar.dart
@@ -0,0 +1,59 @@
+import "package:flutter/material.dart";
+
+void showSuccessSnackbar(BuildContext context, String message) {
+ final snackBar = SnackBar(
+ content: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 8.0),
+ child: Text(
+ message,
+ style: const TextStyle(
+ color: Colors.white,
+ fontSize: 16.0,
+ ),
+ ),
+ ),
+ backgroundColor: Colors.green,
+ elevation: 6.0,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(10.0),
+ ),
+ action: SnackBarAction(
+ label: 'OK',
+ onPressed: () {
+ ScaffoldMessenger.of(context).hideCurrentSnackBar();
+ },
+ ),
+ duration: const Duration(seconds: 2), // Set the duration here
+ );
+
+ ScaffoldMessenger.of(context).showSnackBar(snackBar);
+}
+
+void showErrorSnackbar(BuildContext context, String message) {
+ final snackBar = SnackBar(
+ content: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 8.0),
+ child: Text(
+ message,
+ style: const TextStyle(
+ color: Colors.white,
+ fontSize: 16.0,
+ ),
+ ),
+ ),
+ backgroundColor: Colors.red,
+ elevation: 6.0,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(10.0),
+ ),
+ action: SnackBarAction(
+ label: 'OK',
+ onPressed: () {
+ ScaffoldMessenger.of(context).hideCurrentSnackBar();
+ },
+ ),
+ duration: const Duration(seconds: 2), // Set the duration here
+ );
+
+ ScaffoldMessenger.of(context).showSnackBar(snackBar);
+}
diff --git a/pubspec.lock b/pubspec.lock
new file mode 100644
index 0000000..c4f7053
--- /dev/null
+++ b/pubspec.lock
@@ -0,0 +1,890 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+ _flutterfire_internals:
+ dependency: transitive
+ description:
+ name: _flutterfire_internals
+ sha256: f5628cd9c92ed11083f425fd1f8f1bc60ecdda458c81d73b143aeda036c35fe7
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.16"
+ archive:
+ dependency: transitive
+ description:
+ name: archive
+ sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.4.10"
+ args:
+ dependency: transitive
+ description:
+ name: args
+ sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.2"
+ async:
+ dependency: transitive
+ description:
+ name: async
+ sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.11.0"
+ boolean_selector:
+ dependency: transitive
+ description:
+ name: boolean_selector
+ sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.1"
+ characters:
+ dependency: transitive
+ description:
+ name: characters
+ sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.0"
+ checked_yaml:
+ dependency: transitive
+ description:
+ name: checked_yaml
+ sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.3"
+ cli_util:
+ dependency: transitive
+ description:
+ name: cli_util
+ sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.4.1"
+ clock:
+ dependency: transitive
+ description:
+ name: clock
+ sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.1"
+ cloud_firestore:
+ dependency: "direct main"
+ description:
+ name: cloud_firestore
+ sha256: "8bfbb5a2edbc6052452326d60de0113fea2bcbf081d34a3f8e45c8b38307b31c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.14.0"
+ cloud_firestore_platform_interface:
+ dependency: transitive
+ description:
+ name: cloud_firestore_platform_interface
+ sha256: "73ff438fe46028f0e19f55da18b6ddc6906ab750562cd7d9ffab77ff8c0c4307"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.1.0"
+ cloud_firestore_web:
+ dependency: transitive
+ description:
+ name: cloud_firestore_web
+ sha256: "232e45e95970d3a6baab8f50f9c3a6e2838d145d9d91ec9a7392837c44296397"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.9.0"
+ collection:
+ dependency: transitive
+ description:
+ name: collection
+ sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.18.0"
+ convert:
+ dependency: transitive
+ description:
+ name: convert
+ sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.1"
+ cross_file:
+ dependency: transitive
+ description:
+ name: cross_file
+ sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.3.3+8"
+ crypto:
+ dependency: transitive
+ description:
+ name: crypto
+ sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.3"
+ cupertino_icons:
+ dependency: "direct main"
+ description:
+ name: cupertino_icons
+ sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.6"
+ device_info_plus:
+ dependency: "direct main"
+ description:
+ name: device_info_plus
+ sha256: "0042cb3b2a76413ea5f8a2b40cec2a33e01d0c937e91f0f7c211fde4f7739ba6"
+ url: "https://pub.dev"
+ source: hosted
+ version: "9.1.1"
+ device_info_plus_platform_interface:
+ dependency: transitive
+ description:
+ name: device_info_plus_platform_interface
+ sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.0.0"
+ fake_async:
+ dependency: transitive
+ description:
+ name: fake_async
+ sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.1"
+ ffi:
+ dependency: transitive
+ description:
+ name: ffi
+ sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.0"
+ file:
+ dependency: transitive
+ description:
+ name: file
+ sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.0.0"
+ file_selector_linux:
+ dependency: transitive
+ description:
+ name: file_selector_linux
+ sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.9.2+1"
+ file_selector_macos:
+ dependency: transitive
+ description:
+ name: file_selector_macos
+ sha256: b15c3da8bd4908b9918111fa486903f5808e388b8d1c559949f584725a6594d6
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.9.3+3"
+ file_selector_platform_interface:
+ dependency: transitive
+ description:
+ name: file_selector_platform_interface
+ sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.6.2"
+ file_selector_windows:
+ dependency: transitive
+ description:
+ name: file_selector_windows
+ sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.9.3+1"
+ firebase_auth:
+ dependency: "direct main"
+ description:
+ name: firebase_auth
+ sha256: "279b2773ff61afd9763202cb5582e2b995ee57419d826b9af6517302a59b672f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.16.0"
+ firebase_auth_platform_interface:
+ dependency: transitive
+ description:
+ name: firebase_auth_platform_interface
+ sha256: "3c9cfaccb7549492edf5b0c67c6dd1c6727c7830891aa6727f2fb225f0226626"
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.0.9"
+ firebase_auth_web:
+ dependency: transitive
+ description:
+ name: firebase_auth_web
+ sha256: c7b1379ccef7abf4b6816eede67a868c44142198e42350f51c01d8fc03f95a7d
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.8.13"
+ firebase_core:
+ dependency: "direct main"
+ description:
+ name: firebase_core
+ sha256: "96607c0e829a581c2a483c658f04e8b159964c3bae2730f73297070bc85d40bb"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.24.2"
+ firebase_core_platform_interface:
+ dependency: transitive
+ description:
+ name: firebase_core_platform_interface
+ sha256: c437ae5d17e6b5cc7981cf6fd458a5db4d12979905f9aafd1fea930428a9fe63
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.0.0"
+ firebase_core_web:
+ dependency: transitive
+ description:
+ name: firebase_core_web
+ sha256: d585bdf3c656c3f7821ba1bd44da5f13365d22fcecaf5eb75c4295246aaa83c0
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.10.0"
+ firebase_storage:
+ dependency: "direct main"
+ description:
+ name: firebase_storage
+ sha256: "75e6cb6bed65138b5bbd86bfd7cf9bc9a175fb0c31aacc400e9203df117ffbe6"
+ url: "https://pub.dev"
+ source: hosted
+ version: "11.6.0"
+ firebase_storage_platform_interface:
+ dependency: transitive
+ description:
+ name: firebase_storage_platform_interface
+ sha256: "545a3a8edf337850403bb0fa03c8074a53deb87c0107d19755c77a82ce07919e"
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.1.3"
+ firebase_storage_web:
+ dependency: transitive
+ description:
+ name: firebase_storage_web
+ sha256: ee6870ff79aa304b8996ba18a4aefe1e8b3fc31fd385eab6574180267aa8d393
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.6.17"
+ flutter:
+ dependency: "direct main"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_dotenv:
+ dependency: "direct main"
+ description:
+ name: flutter_dotenv
+ sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77"
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.1.0"
+ flutter_launcher_icons:
+ dependency: "direct main"
+ description:
+ name: flutter_launcher_icons
+ sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.13.1"
+ flutter_lints:
+ dependency: "direct dev"
+ description:
+ name: flutter_lints
+ sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.3"
+ flutter_plugin_android_lifecycle:
+ dependency: transitive
+ description:
+ name: flutter_plugin_android_lifecycle
+ sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.17"
+ flutter_test:
+ dependency: "direct dev"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_web_plugins:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ google_identity_services_web:
+ dependency: transitive
+ description:
+ name: google_identity_services_web
+ sha256: "0c56c2c5d60d6dfaf9725f5ad4699f04749fb196ee5a70487a46ef184837ccf6"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.3.0+2"
+ google_sign_in:
+ dependency: "direct main"
+ description:
+ name: google_sign_in
+ sha256: "0b8787cb9c1a68ad398e8010e8c8766bfa33556d2ab97c439fb4137756d7308f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.2.1"
+ google_sign_in_android:
+ dependency: transitive
+ description:
+ name: google_sign_in_android
+ sha256: bfd42c81c30c6faba16e0f62968d5505a87504aaa672b3155ee931461abb0a49
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.1.21"
+ google_sign_in_ios:
+ dependency: transitive
+ description:
+ name: google_sign_in_ios
+ sha256: f3336d9e44d4d28063ac90271f6db5caf99f0480cb07281330e7a432edb95226
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.7.3"
+ google_sign_in_platform_interface:
+ dependency: transitive
+ description:
+ name: google_sign_in_platform_interface
+ sha256: "1f6e5787d7a120cc0359ddf315c92309069171306242e181c09472d1b00a2971"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.5"
+ google_sign_in_web:
+ dependency: transitive
+ description:
+ name: google_sign_in_web
+ sha256: a278ea2d01013faf341cbb093da880d0f2a552bbd1cb6ee90b5bebac9ba69d77
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.12.3+2"
+ http:
+ dependency: transitive
+ description:
+ name: http
+ sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
+ http_parser:
+ dependency: transitive
+ description:
+ name: http_parser
+ sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.0.2"
+ image:
+ dependency: transitive
+ description:
+ name: image
+ sha256: "004a2e90ce080f8627b5a04aecb4cdfac87d2c3f3b520aa291260be5a32c033d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.1.4"
+ image_picker:
+ dependency: "direct main"
+ description:
+ name: image_picker
+ sha256: "26222b01a0c9a2c8fe02fc90b8208bd3325da5ed1f4a2acabf75939031ac0bdd"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.7"
+ image_picker_android:
+ dependency: transitive
+ description:
+ name: image_picker_android
+ sha256: "39f2bfe497e495450c81abcd44b62f56c2a36a37a175da7d137b4454977b51b1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.8.9+3"
+ image_picker_for_web:
+ dependency: transitive
+ description:
+ name: image_picker_for_web
+ sha256: e2423c53a68b579a7c37a1eda967b8ae536c3d98518e5db95ca1fe5719a730a3
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.2"
+ image_picker_ios:
+ dependency: transitive
+ description:
+ name: image_picker_ios
+ sha256: fadafce49e8569257a0cad56d24438a6fa1f0cbd7ee0af9b631f7492818a4ca3
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.8.9+1"
+ image_picker_linux:
+ dependency: transitive
+ description:
+ name: image_picker_linux
+ sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.1+1"
+ image_picker_macos:
+ dependency: transitive
+ description:
+ name: image_picker_macos
+ sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.1+1"
+ image_picker_platform_interface:
+ dependency: transitive
+ description:
+ name: image_picker_platform_interface
+ sha256: fa4e815e6fcada50e35718727d83ba1c92f1edf95c0b4436554cec301b56233b
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.9.3"
+ image_picker_windows:
+ dependency: transitive
+ description:
+ name: image_picker_windows
+ sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.1+1"
+ intl:
+ dependency: "direct main"
+ description:
+ name: intl
+ sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.19.0"
+ js:
+ dependency: transitive
+ description:
+ name: js
+ sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.6.7"
+ json_annotation:
+ dependency: transitive
+ description:
+ name: json_annotation
+ sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.8.1"
+ lints:
+ dependency: transitive
+ description:
+ name: lints
+ sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.1"
+ matcher:
+ dependency: transitive
+ description:
+ name: matcher
+ sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.12.16"
+ material_color_utilities:
+ dependency: transitive
+ description:
+ name: material_color_utilities
+ sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.5.0"
+ meta:
+ dependency: transitive
+ description:
+ name: meta
+ sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.10.0"
+ mime:
+ dependency: transitive
+ description:
+ name: mime
+ sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.4"
+ path:
+ dependency: transitive
+ description:
+ name: path
+ sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.8.3"
+ path_provider_linux:
+ dependency: transitive
+ description:
+ name: path_provider_linux
+ sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.1"
+ path_provider_platform_interface:
+ dependency: transitive
+ description:
+ name: path_provider_platform_interface
+ sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.2"
+ path_provider_windows:
+ dependency: transitive
+ description:
+ name: path_provider_windows
+ sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.1"
+ permission_handler:
+ dependency: "direct main"
+ description:
+ name: permission_handler
+ sha256: "45ff3fbcb99040fde55c528d5e3e6ca29171298a85436274d49c6201002087d6"
+ url: "https://pub.dev"
+ source: hosted
+ version: "11.2.0"
+ permission_handler_android:
+ dependency: transitive
+ description:
+ name: permission_handler_android
+ sha256: "758284a0976772f9c744d6384fc5dc4834aa61e3f7aa40492927f244767374eb"
+ url: "https://pub.dev"
+ source: hosted
+ version: "12.0.3"
+ permission_handler_apple:
+ dependency: transitive
+ description:
+ name: permission_handler_apple
+ sha256: c6bf440f80acd2a873d3d91a699e4cc770f86e7e6b576dda98759e8b92b39830
+ url: "https://pub.dev"
+ source: hosted
+ version: "9.3.0"
+ permission_handler_html:
+ dependency: transitive
+ description:
+ name: permission_handler_html
+ sha256: "54bf176b90f6eddd4ece307e2c06cf977fb3973719c35a93b85cc7093eb6070d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.1.1"
+ permission_handler_platform_interface:
+ dependency: transitive
+ description:
+ name: permission_handler_platform_interface
+ sha256: "5c43148f2bfb6d14c5a8162c0a712afe891f2d847f35fcff29c406b37da43c3c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.1.0"
+ permission_handler_windows:
+ dependency: transitive
+ description:
+ name: permission_handler_windows
+ sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.1"
+ persistent_bottom_nav_bar:
+ dependency: "direct main"
+ description:
+ name: persistent_bottom_nav_bar
+ sha256: e2e031e3366fde01511e372a9a19d720a9b252bb456e5c8d77a30d7a4a2ddfb0
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.0.2"
+ petitparser:
+ dependency: transitive
+ description:
+ name: petitparser
+ sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.0.2"
+ platform:
+ dependency: transitive
+ description:
+ name: platform
+ sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.4"
+ plugin_platform_interface:
+ dependency: transitive
+ description:
+ name: plugin_platform_interface
+ sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.8"
+ pointycastle:
+ dependency: transitive
+ description:
+ name: pointycastle
+ sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.7.4"
+ shared_preferences:
+ dependency: "direct main"
+ description:
+ name: shared_preferences
+ sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.2"
+ shared_preferences_android:
+ dependency: transitive
+ description:
+ name: shared_preferences_android
+ sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.1"
+ shared_preferences_foundation:
+ dependency: transitive
+ description:
+ name: shared_preferences_foundation
+ sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.5"
+ shared_preferences_linux:
+ dependency: transitive
+ description:
+ name: shared_preferences_linux
+ sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.2"
+ shared_preferences_platform_interface:
+ dependency: transitive
+ description:
+ name: shared_preferences_platform_interface
+ sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.2"
+ shared_preferences_web:
+ dependency: transitive
+ description:
+ name: shared_preferences_web
+ sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.2"
+ shared_preferences_windows:
+ dependency: transitive
+ description:
+ name: shared_preferences_windows
+ sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.2"
+ sky_engine:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.99"
+ source_span:
+ dependency: transitive
+ description:
+ name: source_span
+ sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.10.0"
+ stack_trace:
+ dependency: transitive
+ description:
+ name: stack_trace
+ sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.11.1"
+ stream_channel:
+ dependency: transitive
+ description:
+ name: stream_channel
+ sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.2"
+ string_scanner:
+ dependency: transitive
+ description:
+ name: string_scanner
+ sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
+ term_glyph:
+ dependency: transitive
+ description:
+ name: term_glyph
+ sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.1"
+ test_api:
+ dependency: transitive
+ description:
+ name: test_api
+ sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.6.1"
+ typed_data:
+ dependency: transitive
+ description:
+ name: typed_data
+ sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.2"
+ url_launcher:
+ dependency: "direct main"
+ description:
+ name: url_launcher
+ sha256: d25bb0ca00432a5e1ee40e69c36c85863addf7cc45e433769d61bed3fe81fd96
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.2.3"
+ url_launcher_android:
+ dependency: transitive
+ description:
+ name: url_launcher_android
+ sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.2.2"
+ url_launcher_ios:
+ dependency: transitive
+ description:
+ name: url_launcher_ios
+ sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.2.4"
+ url_launcher_linux:
+ dependency: transitive
+ description:
+ name: url_launcher_linux
+ sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.1"
+ url_launcher_macos:
+ dependency: transitive
+ description:
+ name: url_launcher_macos
+ sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.0"
+ url_launcher_platform_interface:
+ dependency: transitive
+ description:
+ name: url_launcher_platform_interface
+ sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.1"
+ url_launcher_web:
+ dependency: transitive
+ description:
+ name: url_launcher_web
+ sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.3"
+ url_launcher_windows:
+ dependency: transitive
+ description:
+ name: url_launcher_windows
+ sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.1"
+ vector_math:
+ dependency: transitive
+ description:
+ name: vector_math
+ sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ web:
+ dependency: transitive
+ description:
+ name: web
+ sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.3.0"
+ win32:
+ dependency: transitive
+ description:
+ name: win32
+ sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8"
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.2.0"
+ win32_registry:
+ dependency: transitive
+ description:
+ name: win32_registry
+ sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.2"
+ xdg_directories:
+ dependency: transitive
+ description:
+ name: xdg_directories
+ sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.4"
+ xml:
+ dependency: transitive
+ description:
+ name: xml
+ sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.5.0"
+ yaml:
+ dependency: transitive
+ description:
+ name: yaml
+ sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.2"
+sdks:
+ dart: ">=3.2.4 <4.0.0"
+ flutter: ">=3.16.0"
diff --git a/pubspec.yaml b/pubspec.yaml
new file mode 100644
index 0000000..e342df7
--- /dev/null
+++ b/pubspec.yaml
@@ -0,0 +1,82 @@
+name: spoonshare
+description: "Meals of Grace."
+publish_to: 'none'
+version: 1.0.0+1
+
+environment:
+ sdk: '>=3.2.4 <4.0.0'
+
+dependencies:
+ flutter:
+ sdk: flutter
+ cupertino_icons: ^1.0.2
+ firebase_core: ^2.24.2
+ firebase_auth: ^4.16.0
+ cloud_firestore: ^4.14.0
+ shared_preferences: ^2.2.2
+ flutter_dotenv: ^5.1.0
+ google_sign_in: ^6.2.1
+ persistent_bottom_nav_bar: ^5.0.2
+ image_picker: ^1.0.7
+ firebase_storage: ^11.6.0
+ permission_handler: ^11.2.0
+ flutter_launcher_icons: ^0.13.1
+ device_info_plus: ^9.1.1
+ url_launcher: ^6.2.3
+ intl: ^0.19.0
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+ flutter_lints: ^2.0.0
+
+flutter:
+
+ uses-material-design: true
+
+ assets:
+ - assets/images/onboarding.png
+ - assets/images/google.png
+ - assets/images/mail.png
+ - assets/images/spoonshare_launcher.png
+ - assets/images/thankyou.jpg
+
+
+flutter_launcher_icons:
+ android: "launcher_icon"
+ ios: true
+ image_path: "assets/images/spoonshare_launcher.png"
+ min_sdk_android: 21
+ web:
+ generate: true
+ image_path: "assets/images/spoonshare_launcher.png"
+ background_color: "#hexcode"
+ theme_color: "#hexcode"
+
+
+
+ # An image asset can refer to one or more resolution-specific "variants", see
+ # https://flutter.dev/assets-and-images/#resolution-aware
+
+ # For details regarding adding assets from package dependencies, see
+ # https://flutter.dev/assets-and-images/#from-packages
+
+ # To add custom fonts to your application, add a fonts section here,
+ # in this "flutter" section. Each entry in this list should have a
+ # "family" key with the font family name, and a "fonts" key with a
+ # list giving the asset and other descriptors for the font. For
+ # example:
+ # fonts:
+ # - family: Schyler
+ # fonts:
+ # - asset: fonts/Schyler-Regular.ttf
+ # - asset: fonts/Schyler-Italic.ttf
+ # style: italic
+ # - family: Trajan Pro
+ # fonts:
+ # - asset: fonts/TrajanPro.ttf
+ # - asset: fonts/TrajanPro_Bold.ttf
+ # weight: 700
+ #
+ # For details regarding fonts from package dependencies,
+ # see https://flutter.dev/custom-fonts/#from-packages
diff --git a/web/favicon.png b/web/favicon.png
new file mode 100644
index 0000000..c8b2331
Binary files /dev/null and b/web/favicon.png differ
diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png
new file mode 100644
index 0000000..d443fae
Binary files /dev/null and b/web/icons/Icon-192.png differ
diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png
new file mode 100644
index 0000000..8b9f694
Binary files /dev/null and b/web/icons/Icon-512.png differ
diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png
new file mode 100644
index 0000000..d443fae
Binary files /dev/null and b/web/icons/Icon-maskable-192.png differ
diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png
new file mode 100644
index 0000000..8b9f694
Binary files /dev/null and b/web/icons/Icon-maskable-512.png differ
diff --git a/web/index.html b/web/index.html
new file mode 100644
index 0000000..f04b7a0
--- /dev/null
+++ b/web/index.html
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SpoonShare
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web/manifest.json b/web/manifest.json
new file mode 100644
index 0000000..07d4859
--- /dev/null
+++ b/web/manifest.json
@@ -0,0 +1,35 @@
+{
+ "name": "spoonsharemeals",
+ "short_name": "spoonsharemeals",
+ "start_url": ".",
+ "display": "standalone",
+ "background_color": "#hexcode",
+ "theme_color": "#hexcode",
+ "description": "SpoonShare - Meal of grace",
+ "orientation": "portrait-primary",
+ "prefer_related_applications": false,
+ "icons": [
+ {
+ "src": "icons/Icon-192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "icons/Icon-512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ },
+ {
+ "src": "icons/Icon-maskable-192.png",
+ "sizes": "192x192",
+ "type": "image/png",
+ "purpose": "maskable"
+ },
+ {
+ "src": "icons/Icon-maskable-512.png",
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "maskable"
+ }
+ ]
+}
\ No newline at end of file