diff --git a/ride_share/.gitignore b/ride_share/.gitignore index 24476c5..1f02e9c 100644 --- a/ride_share/.gitignore +++ b/ride_share/.gitignore @@ -37,8 +37,13 @@ app.*.symbols # Obfuscation related app.*.map.json +config.json # Android Studio will place build artifacts here /android/app/debug /android/app/profile /android/app/release + +#Secrets file for API keys +/lib/auth/secrets.dart +/lib/auth.config.dart diff --git a/ride_share/android/app/build.gradle b/ride_share/android/app/build.gradle index cab9e96..af8b9ef 100644 --- a/ride_share/android/app/build.gradle +++ b/ride_share/android/app/build.gradle @@ -21,6 +21,12 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } +def envVariables = [ + MAPS_API_KEY: project.hasProperty('MAPS_API_KEY') + ? MAPS_API_KEY + : "API_KEY", +]; + apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" @@ -47,10 +53,11 @@ android { applicationId "com.example.ride_share" // 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 + minSdkVersion 23 targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName + resValue "string", "googleMapApiKey", envVariables.MAPS_API_KEY } buildTypes { @@ -68,4 +75,5 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'com.google.android.gms:play-services-maps:18.1.0' } diff --git a/ride_share/android/app/google-services.json b/ride_share/android/app/google-services.json new file mode 100644 index 0000000..cf27c0c --- /dev/null +++ b/ride_share/android/app/google-services.json @@ -0,0 +1,99 @@ +{ + "project_info": { + "project_number": "943152405737", + "firebase_url": "https://school-carpool-csp1-default-rtdb.europe-west1.firebasedatabase.app", + "project_id": "school-carpool-csp1", + "storage_bucket": "school-carpool-csp1.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:943152405737:android:5457e3555963406fdce478", + "android_client_info": { + "package_name": "com.example.ride_share" + } + }, + "oauth_client": [ + { + "client_id": "943152405737-dl3gep47gr3a3m6e6uhjfmctev8nq0rd.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.example.ride_share", + "certificate_hash": "0cef302226b6059fc5e61d390fbeeba8fc32c916" + } + }, + { + "client_id": "943152405737-1ibrmd1ee903soduuo0cgvfoqag8k98n.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCZy5aFxa7OGpgnAFvVX-ozsCZ71DJWCbA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "943152405737-1ibrmd1ee903soduuo0cgvfoqag8k98n.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "943152405737-jheatv57d64h92jk6hlugo81ivhpqhq2.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.example.rideShare" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:943152405737:android:1756b43b64699837dce478", + "android_client_info": { + "package_name": "com.school.testrun" + } + }, + "oauth_client": [ + { + "client_id": "943152405737-4igp2ltm5gqsco0stb3r06e52v12sha0.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.school.testrun", + "certificate_hash": "0cef302226b6059fc5e61d390fbeeba8fc32c916" + } + }, + { + "client_id": "943152405737-1ibrmd1ee903soduuo0cgvfoqag8k98n.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCZy5aFxa7OGpgnAFvVX-ozsCZ71DJWCbA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "943152405737-1ibrmd1ee903soduuo0cgvfoqag8k98n.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "943152405737-jheatv57d64h92jk6hlugo81ivhpqhq2.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.example.rideShare" + } + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/ride_share/android/app/src/main/AndroidManifest.xml b/ride_share/android/app/src/main/AndroidManifest.xml index 9abd6f9..dacd20d 100644 --- a/ride_share/android/app/src/main/AndroidManifest.xml +++ b/ride_share/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,8 @@ - + + @@ -17,9 +19,9 @@ while the Flutter UI initializes. After that, this theme continues to determine the Window background behind the Flutter UI. --> + android:name="io.flutter.embedding.android.NormalTheme" + android:resource="@style/NormalTheme" + /> @@ -30,5 +32,7 @@ + diff --git a/ride_share/android/app/src/main/kotlin/com/example/ride_share/MainActivity.kt b/ride_share/android/app/src/main/kotlin/com/example/ride_share/MainActivity.kt index ca04e26..c4fe69f 100644 --- a/ride_share/android/app/src/main/kotlin/com/example/ride_share/MainActivity.kt +++ b/ride_share/android/app/src/main/kotlin/com/example/ride_share/MainActivity.kt @@ -1,6 +1,6 @@ package com.example.ride_share -import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.android.FlutterFragmentActivity -class MainActivity: FlutterActivity() { +class MainActivity: FlutterFragmentActivity() { } diff --git a/ride_share/assets/Badge (2).png b/ride_share/assets/Badge (2).png new file mode 100644 index 0000000..00c2641 Binary files /dev/null and b/ride_share/assets/Badge (2).png differ diff --git a/ride_share/assets/Pin_current_location.png b/ride_share/assets/Pin_current_location.png new file mode 100644 index 0000000..e8b3dc3 Binary files /dev/null and b/ride_share/assets/Pin_current_location.png differ diff --git a/ride_share/assets/Pin_destination.png b/ride_share/assets/Pin_destination.png new file mode 100644 index 0000000..315fc8e Binary files /dev/null and b/ride_share/assets/Pin_destination.png differ diff --git a/ride_share/assets/Pin_source.png b/ride_share/assets/Pin_source.png new file mode 100644 index 0000000..1705029 Binary files /dev/null and b/ride_share/assets/Pin_source.png differ diff --git a/ride_share/assets/images/profile.png b/ride_share/assets/images/profile.png new file mode 100644 index 0000000..1c927a4 Binary files /dev/null and b/ride_share/assets/images/profile.png differ diff --git a/ride_share/assets/map_style.txt b/ride_share/assets/map_style.txt new file mode 100644 index 0000000..454740d --- /dev/null +++ b/ride_share/assets/map_style.txt @@ -0,0 +1,218 @@ +[ + { + "elementType": "geometry", + "stylers": [ + { + "color": "#f5f5f5" + } + ] + }, + { + "elementType": "labels", + "stylers": [ + { + "visibility": "off" + } + ] + }, + { + "elementType": "labels.icon", + "stylers": [ + { + "visibility": "off" + } + ] + }, + { + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#616161" + } + ] + }, + { + "elementType": "labels.text.stroke", + "stylers": [ + { + "color": "#f5f5f5" + } + ] + }, + { + "featureType": "administrative", + "elementType": "geometry", + "stylers": [ + { + "visibility": "off" + } + ] + }, + { + "featureType": "administrative.land_parcel", + "stylers": [ + { + "visibility": "off" + } + ] + }, + { + "featureType": "administrative.land_parcel", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#bdbdbd" + } + ] + }, + { + "featureType": "administrative.neighborhood", + "stylers": [ + { + "visibility": "off" + } + ] + }, + { + "featureType": "poi", + "stylers": [ + { + "visibility": "off" + } + ] + }, + { + "featureType": "poi", + "elementType": "geometry", + "stylers": [ + { + "color": "#eeeeee" + } + ] + }, + { + "featureType": "poi", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#757575" + } + ] + }, + { + "featureType": "poi.park", + "elementType": "geometry", + "stylers": [ + { + "color": "#e5e5e5" + } + ] + }, + { + "featureType": "poi.park", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#9e9e9e" + } + ] + }, + { + "featureType": "road", + "elementType": "geometry", + "stylers": [ + { + "color": "#ffffff" + } + ] + }, + { + "featureType": "road", + "elementType": "labels.icon", + "stylers": [ + { + "visibility": "off" + } + ] + }, + { + "featureType": "road.arterial", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#757575" + } + ] + }, + { + "featureType": "road.highway", + "elementType": "geometry", + "stylers": [ + { + "color": "#dadada" + } + ] + }, + { + "featureType": "road.highway", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#616161" + } + ] + }, + { + "featureType": "road.local", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#9e9e9e" + } + ] + }, + { + "featureType": "transit", + "stylers": [ + { + "visibility": "off" + } + ] + }, + { + "featureType": "transit.line", + "elementType": "geometry", + "stylers": [ + { + "color": "#e5e5e5" + } + ] + }, + { + "featureType": "transit.station", + "elementType": "geometry", + "stylers": [ + { + "color": "#eeeeee" + } + ] + }, + { + "featureType": "water", + "elementType": "geometry", + "stylers": [ + { + "color": "#c9c9c9" + } + ] + }, + { + "featureType": "water", + "elementType": "labels.text.fill", + "stylers": [ + { + "color": "#9e9e9e" + } + ] + } +] diff --git a/ride_share/ios/Flutter/Debug.xcconfig b/ride_share/ios/Flutter/Debug.xcconfig index 592ceee..f402a8f 100644 --- a/ride_share/ios/Flutter/Debug.xcconfig +++ b/ride_share/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include "Default.xcconfig" #include "Generated.xcconfig" diff --git a/ride_share/ios/Flutter/Default.xcconfig b/ride_share/ios/Flutter/Default.xcconfig new file mode 100644 index 0000000..d1fa0eb --- /dev/null +++ b/ride_share/ios/Flutter/Default.xcconfig @@ -0,0 +1 @@ +MAPS_API_KEY=API_KEY \ No newline at end of file diff --git a/ride_share/ios/Flutter/Release.xcconfig b/ride_share/ios/Flutter/Release.xcconfig index 592ceee..f402a8f 100644 --- a/ride_share/ios/Flutter/Release.xcconfig +++ b/ride_share/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include "Default.xcconfig" #include "Generated.xcconfig" diff --git a/ride_share/ios/Runner/GoogleService-Info.plist b/ride_share/ios/Runner/GoogleService-Info.plist new file mode 100644 index 0000000..53c88d1 --- /dev/null +++ b/ride_share/ios/Runner/GoogleService-Info.plist @@ -0,0 +1,38 @@ + + + + + CLIENT_ID + 943152405737-jheatv57d64h92jk6hlugo81ivhpqhq2.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.943152405737-jheatv57d64h92jk6hlugo81ivhpqhq2 + ANDROID_CLIENT_ID + 943152405737-4igp2ltm5gqsco0stb3r06e52v12sha0.apps.googleusercontent.com + API_KEY + + GCM_SENDER_ID + 943152405737 + PLIST_VERSION + 1 + BUNDLE_ID + com.example.rideShare + PROJECT_ID + school-carpool-csp1 + STORAGE_BUCKET + school-carpool-csp1.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:943152405737:ios:f1b1f4fc2facef5bdce478 + DATABASE_URL + https://school-carpool-csp1-default-rtdb.europe-west1.firebasedatabase.app + + \ No newline at end of file diff --git a/ride_share/ios/Runner/Info.plist b/ride_share/ios/Runner/Info.plist index 10f511b..baf0edd 100644 --- a/ride_share/ios/Runner/Info.plist +++ b/ride_share/ios/Runner/Info.plist @@ -7,6 +7,9 @@ CFBundleDisplayName Ride Share CFBundleExecutable + CFBundleDisplayName + ${MAPS_API_KEY} + CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) diff --git a/ride_share/ios/firebase_app_id_file.json b/ride_share/ios/firebase_app_id_file.json new file mode 100644 index 0000000..6726fb5 --- /dev/null +++ b/ride_share/ios/firebase_app_id_file.json @@ -0,0 +1,7 @@ +{ + "file_generated_by": "FlutterFire CLI", + "purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory", + "GOOGLE_APP_ID": "1:943152405737:ios:f1b1f4fc2facef5bdce478", + "FIREBASE_PROJECT_ID": "school-carpool-csp1", + "GCM_SENDER_ID": "943152405737" +} \ No newline at end of file diff --git a/ride_share/lib/firebase_options.dart b/ride_share/lib/firebase_options.dart new file mode 100644 index 0000000..6c8bcae --- /dev/null +++ b/ride_share/lib/firebase_options.dart @@ -0,0 +1,80 @@ +// File generated by FlutterFire CLI. +// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; +import 'package:flutter/foundation.dart' + show defaultTargetPlatform, kIsWeb, TargetPlatform; + +/// Default [FirebaseOptions] for use with your Firebase apps. +/// +/// Example: +/// ```dart +/// import 'firebase_options.dart'; +/// // ... +/// await Firebase.initializeApp( +/// options: DefaultFirebaseOptions.currentPlatform, +/// ); +/// ``` +class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + return web; + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + return ios; + case TargetPlatform.macOS: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for macos - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.windows: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for windows - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.linux: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for linux - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + default: + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + } + } + + static const FirebaseOptions web = FirebaseOptions( + apiKey: '', + appId: '1:943152405737:web:ae3cf64ad426bde0dce478', + messagingSenderId: '943152405737', + projectId: 'school-carpool-csp1', + authDomain: 'school-carpool-csp1.firebaseapp.com', + databaseURL: 'https://school-carpool-csp1-default-rtdb.europe-west1.firebasedatabase.app', + storageBucket: 'school-carpool-csp1.appspot.com', + measurementId: 'G-X5NJCDV6TM', + ); + + static const FirebaseOptions android = FirebaseOptions( + apiKey: '', + appId: '1:943152405737:android:5457e3555963406fdce478', + messagingSenderId: '943152405737', + projectId: 'school-carpool-csp1', + databaseURL: 'https://school-carpool-csp1-default-rtdb.europe-west1.firebasedatabase.app', + storageBucket: 'school-carpool-csp1.appspot.com', + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: '', + appId: '1:943152405737:ios:f1b1f4fc2facef5bdce478', + messagingSenderId: '943152405737', + projectId: 'school-carpool-csp1', + databaseURL: 'https://school-carpool-csp1-default-rtdb.europe-west1.firebasedatabase.app', + storageBucket: 'school-carpool-csp1.appspot.com', + androidClientId: '943152405737-4igp2ltm5gqsco0stb3r06e52v12sha0.apps.googleusercontent.com', + iosClientId: '943152405737-jheatv57d64h92jk6hlugo81ivhpqhq2.apps.googleusercontent.com', + iosBundleId: 'com.example.rideShare', + ); +} diff --git a/ride_share/lib/main.dart b/ride_share/lib/main.dart index b432ff0..420997f 100644 --- a/ride_share/lib/main.dart +++ b/ride_share/lib/main.dart @@ -1,10 +1,15 @@ +import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:ride_share/firebase_options.dart'; import 'package:ride_share/src/features/authentication/screens/splash_screen/splash_screen.dart'; import 'package:ride_share/src/features/authentication/screens/welcome/welcome_screen.dart'; +import 'package:ride_share/src/repository/authentication_repository.dart'; import 'package:ride_share/src/utils/theme/theme.dart'; void main() { + WidgetsFlutterBinding.ensureInitialized(); + Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform).then((value) => Get.put(AuthenticationRepository())); runApp(const MyApp()); } diff --git a/ride_share/lib/src/common_widgets/location_list_tile.dart b/ride_share/lib/src/common_widgets/location_list_tile.dart new file mode 100644 index 0000000..ed3439b --- /dev/null +++ b/ride_share/lib/src/common_widgets/location_list_tile.dart @@ -0,0 +1,37 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class LocationListTile extends StatelessWidget{ + final String location; + final VoidCallback press; + + const LocationListTile({ + Key? key, + required this.location, + required this.press, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + ListTile( + onTap: press, + horizontalTitleGap: 0, + leading: const Icon( Icons.location_on_outlined,), + title: Text( + location, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + const Divider( + height: 2, + thickness: 2, + color: Colors.grey, + ), + ], + ); + } + +} \ No newline at end of file diff --git a/ride_share/lib/src/common_widgets/nav_drawer.dart b/ride_share/lib/src/common_widgets/nav_drawer.dart new file mode 100644 index 0000000..63167df --- /dev/null +++ b/ride_share/lib/src/common_widgets/nav_drawer.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:ride_share/src/constants/colors.dart'; +import 'package:ride_share/src/constants/text_strings.dart'; +import 'package:ride_share/src/features/home/screens/profile/profile_screen.dart'; + +import '../repository/authentication_repository.dart'; + +class NavDrawer extends StatelessWidget{ + const NavDrawer({super.key}); + + @override + Widget build(BuildContext context) { + return Drawer( + child: ListView( + padding: EdgeInsets.zero, + children: [ + DrawerHeader( + child: ListTile( + leading: Icon( + size: 100, + Icons.person_outline + ), + title: Text(tEditProfile), + onTap: () => Get.to(() => const ProfileScreen()), + ), + ), + ListTile( + leading: Icon(Icons.wallet_outlined), + title: Text(tPayment), + onTap: () => {}, + ), + ListTile( + leading: Icon(Icons.calendar_month), + title: Text(tMySchedule), + onTap: () => {Navigator.of(context).pop()}, + ), + ListTile( + leading: Icon(Icons.history), + title: Text(tTripHistory), + onTap: () => {Navigator.of(context).pop()}, + ), + ListTile( + leading: Icon(Icons.people_alt_outlined), + title: Text(tRideRequests), + onTap: () => {Navigator.of(context).pop()}, + ), + Divider( + height: 2, + thickness: 1, + color: Colors.grey, + ), + ListTile( + leading: Icon(Icons.contact_support_outlined), + title: Text(tSupport), + onTap: () => {Navigator.of(context).pop()}, + ), + ListTile( + leading: Icon(Icons.logout, color: Colors.red,), + title: Text(tAbout), + onTap: () => {AuthenticationRepository.instance.logout()}, + ), + ], + ), + ); + } + +} \ No newline at end of file diff --git a/ride_share/lib/src/common_widgets/registration_header.dart b/ride_share/lib/src/common_widgets/registration_header.dart new file mode 100644 index 0000000..326b550 --- /dev/null +++ b/ride_share/lib/src/common_widgets/registration_header.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:ride_share/src/constants/colors.dart'; + +Widget regHeaderPlain({required String title, String? subtitle}) { + return Container( + width: Get.width, + decoration: BoxDecoration( + color: tSecondaryColor, + ), + height: Get.height * 0.25, + child: Container( + height: Get.height * 0.1, + width: Get.width, + margin: EdgeInsets.only(top: Get.height * 0.05), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + title, + style: GoogleFonts.poppins( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.white), + ), + if (subtitle != null) + Text( + subtitle, + style: GoogleFonts.poppins( + fontSize: 16, + fontWeight: FontWeight.w400, + color: Colors.white), + ), + ], + )), + ); +} diff --git a/ride_share/lib/src/common_widgets/text_widget.dart b/ride_share/lib/src/common_widgets/text_widget.dart new file mode 100644 index 0000000..a662ab6 --- /dev/null +++ b/ride_share/lib/src/common_widgets/text_widget.dart @@ -0,0 +1,6 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +Widget textWidget({required String text,double fontSize = 12, FontWeight fontWeight = FontWeight.normal,Color color = Colors.black}){ + return Text(text, style: GoogleFonts.poppins(fontSize: fontSize,fontWeight: fontWeight,color: color),); +} \ No newline at end of file diff --git a/ride_share/lib/src/constants/image_strings.dart b/ride_share/lib/src/constants/image_strings.dart index 6ea98a6..50594b6 100644 --- a/ride_share/lib/src/constants/image_strings.dart +++ b/ride_share/lib/src/constants/image_strings.dart @@ -4,4 +4,9 @@ const String tLoginScreenImage = "assets/images/login.png"; const String tSignUpScreenImage = "assets/images/signup.png"; const String tResetPwdScreenImage = "assets/images/reset_pwd.png"; const String tForgotPwdScreenImage = "assets/images/forgot_pwd.png"; -const String tOTPScreenImage = "assets/images/enter_OTP.png"; \ No newline at end of file +const String tOTPScreenImage = "assets/images/enter_OTP.png"; +const String tProfilePic = "assets/images/profile.png"; +const String tcurrentloc = "assets/images/Pin_current_location.png"; +const String tpindest = "assets/images/Pin_destination.png"; +const String tpinsource = "assets/images/Pin_source.png"; +const String tbadge = "assets/images/Badge (2).png"; \ No newline at end of file diff --git a/ride_share/lib/src/constants/text_strings.dart b/ride_share/lib/src/constants/text_strings.dart index e9a1f80..17a8cea 100644 --- a/ride_share/lib/src/constants/text_strings.dart +++ b/ride_share/lib/src/constants/text_strings.dart @@ -40,3 +40,24 @@ const String tOtpSubTitle = "Verification"; const String tOtpMessage = "Enter the verification code sent to\n"; const String tOtpNotSent = "Haven't received a code?"; const String tResendOtp = "Resend"; + + +const String tPickUpLocation = "Pickup Location"; +const String tRating = "Rating"; +const String tEditProfile = "Edit Profile"; +const String tPayment = "Payment"; +const String tMySchedule = "My Schedule"; +const String tSupport = "Support"; +const String tAbout = "About"; +const String tTripHistory = "Trip History"; +const String tRideRequests = "Ride Requests"; +const String tProfile = "Profile"; +const String tVerify = "Verify"; +const String tChange = "change"; +const String tLocations = "Locations"; +const String tAddHome = "Add a home location"; +const String tAddPickup = "Add a pickup location"; +const String tLanguage = "Language"; +const String tLanguageUK = "English - UK"; +const String tLogout = "Logout"; +const String tDeleteAcc = "Delete Account"; diff --git a/ride_share/lib/src/features/authentication/controllers/login_controller.dart b/ride_share/lib/src/features/authentication/controllers/login_controller.dart index c4ec16d..1b40478 100644 --- a/ride_share/lib/src/features/authentication/controllers/login_controller.dart +++ b/ride_share/lib/src/features/authentication/controllers/login_controller.dart @@ -3,10 +3,8 @@ import 'package:get/get.dart'; class LoginController extends GetxController{ static LoginController get instance => Get.find(); - // - // final userRepo = Get.put(UserRepository()); - final email = TextEditingController(); + final phone = TextEditingController(); final password = TextEditingController(); } \ No newline at end of file diff --git a/ride_share/lib/src/features/authentication/controllers/otp_controller.dart b/ride_share/lib/src/features/authentication/controllers/otp_controller.dart new file mode 100644 index 0000000..d368477 --- /dev/null +++ b/ride_share/lib/src/features/authentication/controllers/otp_controller.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:ride_share/src/features/authentication/screens/forgot_pwd/password_reset_screen.dart'; +import 'package:ride_share/src/features/home/screens/home_screen.dart'; + +import '../../../repository/authentication_repository.dart'; + +class OTPController extends GetxController{ + static OTPController get instance => Get.find(); + + final newPassword = TextEditingController(); + final confirmPassword = TextEditingController(); + + void verifyOTP(String otp) async{ + var isVerified = await AuthenticationRepository.instance.verifyOTP(otp); + isVerified ? Get.offAll(const HomeScreen()) : Get.back(); + } + + void verifyForgotPwdOTP(String otp, String phoneNo) async{ + var isVerified = await AuthenticationRepository.instance.verifyOTP(otp); + isVerified ? Get.offAll(PasswordResetScreen(phoneNo: phoneNo)) : Get.back(); + } + + void verifyEmailOTP(String otp, String email) async{ + var isVerified = await AuthenticationRepository.instance.verifyEmailOTP(email, otp); + isVerified ? Get.offAll(PasswordResetScreen(phoneNo: email)) : Get.back(); + } +} \ No newline at end of file diff --git a/ride_share/lib/src/features/authentication/controllers/signup_controller.dart b/ride_share/lib/src/features/authentication/controllers/signup_controller.dart index f528f5a..962d720 100644 --- a/ride_share/lib/src/features/authentication/controllers/signup_controller.dart +++ b/ride_share/lib/src/features/authentication/controllers/signup_controller.dart @@ -1,16 +1,21 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import '../../../models/user_model.dart'; +import '../../../repository/authentication_repository.dart'; +import '../../../repository/user_repository.dart'; +import '../screens/forgot_pwd/otp_screen.dart'; + class SignUpController extends GetxController { static SignUpController get instance => Get.find(); - final firstName = TextEditingController(); + final fullName = TextEditingController(); final lastName = TextEditingController(); final email = TextEditingController(); final phoneNo = TextEditingController(); final password = TextEditingController(); - // final userRepo = Get.put(UserRepository()); + final userRepo = Get.put(UserRepository()); // void registerUser(String email, String password, String phoneNo) { // String? error = AuthenticationRepository.instance @@ -20,20 +25,19 @@ class SignUpController extends GetxController { // } // } - // Future createUser(UserModel user) async { - // await userRepo.createUser(user); - // AuthenticationRepository.instance.createUserWithEmailAndPassword(user.email, user.password!); - // // phoneAuthentication(user.phoneNo); - // // Get.to(() => const OTPScreen()); - // } + Future createUser(UserModel user) async { + await userRepo.createUser(user); + print(user.phoneNo); + await phoneAuthentication(user.phoneNo); + // AuthenticationRepository.instance.createUserWithEmailAndPassword(user.email, user.password!) as bool; + Get.to(() => OTPScreen(phoneNo: user.phoneNo)); + } - // Future phoneAuthentication(String fullname, String email, String phone)async { - // await userRepo.createRealTimeUser(fullname, email, phone); - // AuthenticationRepository.instance.phoneAuthentication(phone); - // } - // phoneAuthentication(String phone) { - // AuthenticationRepository.instance.phoneAuthentication(phone); - // } + Future phoneAuthentication(String phone)async { + // await userRepo.createRealTimeUser(fullname, email, phone); + AuthenticationRepository.instance.phoneAuthentication(phone); + } + } \ No newline at end of file diff --git a/ride_share/lib/src/features/authentication/screens/forgot_pwd/email_otp_screen.dart b/ride_share/lib/src/features/authentication/screens/forgot_pwd/email_otp_screen.dart new file mode 100644 index 0000000..4de8be5 --- /dev/null +++ b/ride_share/lib/src/features/authentication/screens/forgot_pwd/email_otp_screen.dart @@ -0,0 +1,138 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_otp_text_field/flutter_otp_text_field.dart'; +import 'package:get/get.dart'; +import 'package:ride_share/src/constants/colors.dart'; +import 'package:ride_share/src/constants/image_strings.dart'; +import 'package:ride_share/src/repository/authentication_repository.dart'; + +import '../../../../constants/sizes.dart'; +import '../../../../constants/text_strings.dart'; +import '../../controllers/otp_controller.dart'; + +// czpwkimeyjxmaujk + +class EmailOTPScreen extends StatelessWidget { + const EmailOTPScreen({super.key, required this.email}); + + final String email; + + @override + Widget build(BuildContext context) { + var otpController = Get.put(OTPController()); + String otp = ""; + final size = MediaQuery.of(context).size; + return SafeArea( + child: Scaffold( + body: SingleChildScrollView( + child: Stack( + children: [ + Column( + children: [ + Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + colors: [ + Colors.orange, + Colors.deepOrange, + ], + ), + ), + height: size.height * 0.45, + alignment: Alignment.center, + child: Image( + alignment: Alignment.bottomCenter, + image: const AssetImage(tOTPScreenImage), + height: size.height * 0.5, + ), + ), + Container( + height: size.height * 0.45, + padding: const EdgeInsets.all(tDefaultSize), + child: Column( + children: [ + Text( + tOtpTitle, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headlineMedium, + textScaleFactor: 1.8, + ), + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: tSecondaryColor, + ), + height: 5, + width: size.width * 0.3, + ), + const SizedBox(height: 20.0), + Text( + "$tOtpMessage $email", + textAlign: TextAlign.center, + ), + const SizedBox(height: 20.0), + OtpTextField( + mainAxisAlignment: MainAxisAlignment.center, + numberOfFields: 6, + fillColor: Colors.black.withOpacity(0.1), + filled: true, + cursorColor: tSecondaryColor, + focusedBorderColor: tSecondaryColor, + onSubmit: (code) { + otp = code; + otpController.verifyEmailOTP(otp, email); + }, + ), + const SizedBox( + height: 30.0, + ), + SizedBox( + width: size.width * 0.75, + child: ElevatedButton( + onPressed: () { + otpController.verifyEmailOTP(otp, email); + }, + child: const Text( + "Next", + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 18, + letterSpacing: 1.5), + ), + ), + ), + const SizedBox( + height: tFormHeight - 10.0, + ), + TextButton( + onPressed: () { + AuthenticationRepository.instance.sendEmailOtp(email); + }, + child: Text.rich( + TextSpan( + text: "$tOtpNotSent ", + style: Theme.of(context).textTheme.bodyMedium, + children: const [ + TextSpan( + text: tResendOtp, + style: TextStyle( + color: tSecondaryColor, + decoration: TextDecoration.underline), + ) + ], + ), + ), + ), + ], + ), + ) + ], + ) + ], + ), + ) + ), + ); + } +} diff --git a/ride_share/lib/src/features/authentication/screens/forgot_pwd/forgot_pwd_mail_screen.dart b/ride_share/lib/src/features/authentication/screens/forgot_pwd/forgot_pwd_mail_screen.dart index 7bb2fa6..7c983f0 100644 --- a/ride_share/lib/src/features/authentication/screens/forgot_pwd/forgot_pwd_mail_screen.dart +++ b/ride_share/lib/src/features/authentication/screens/forgot_pwd/forgot_pwd_mail_screen.dart @@ -1,21 +1,57 @@ +import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; +import 'package:get/get.dart'; import 'package:ride_share/src/constants/colors.dart'; import 'package:ride_share/src/constants/image_strings.dart'; import 'package:ride_share/src/constants/text_strings.dart'; +import 'package:ride_share/src/features/authentication/screens/forgot_pwd/email_otp_screen.dart'; +import 'package:ride_share/src/repository/authentication_repository.dart'; import '../../../../common_widgets/form_header_widget.dart'; import '../../../../constants/sizes.dart'; +import 'forgot_pwd_otp_screen.dart'; class ForgotPasswordMailScreen extends StatefulWidget { const ForgotPasswordMailScreen({super.key}); @override State createState() => _ForgotPasswordMailScreenState(); + + } + + class _ForgotPasswordMailScreenState extends State { final controller = TextEditingController(); + @override + void dispose(){ + controller.dispose(); + super.dispose(); + } + + // Future passwordReset() async{ + // try{ + // await FirebaseAuth.instance.sendPasswordResetEmail(email: controller.text.trim()); + // showDialog( + // context: context, + // builder: (context){ + // return const AlertDialog( + // content: Text("A password reset link has been sent to your email."), + // ); + // }); + // } on FirebaseAuthException catch (e){ + // showDialog( + // context: context, + // builder: (context){ + // return AlertDialog( + // content: Text(e.message.toString()), + // ); + // }); + // } + // } + @override Widget build(BuildContext context) { return SafeArea( @@ -54,10 +90,11 @@ class _ForgotPasswordMailScreenState extends State { SizedBox( width: double.infinity, child: ElevatedButton( - onPressed: () { - + onPressed: (){ + AuthenticationRepository.instance.sendEmailOtp(controller.text.trim()); + Get.to(() => EmailOTPScreen(email: controller.text.trim())); }, - child: const Text("Next") + child: const Text("Send") ), ) ], @@ -71,11 +108,6 @@ class _ForgotPasswordMailScreenState extends State { ); } - @override - void dispose() { - super.dispose(); - controller.dispose(); - } // Future passwordReset() async{ // try{ diff --git a/ride_share/lib/src/features/authentication/screens/forgot_pwd/forgot_pwd_otp_screen.dart b/ride_share/lib/src/features/authentication/screens/forgot_pwd/forgot_pwd_otp_screen.dart new file mode 100644 index 0000000..5f34fd2 --- /dev/null +++ b/ride_share/lib/src/features/authentication/screens/forgot_pwd/forgot_pwd_otp_screen.dart @@ -0,0 +1,136 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_otp_text_field/flutter_otp_text_field.dart'; +import 'package:get/get.dart'; +import 'package:ride_share/src/constants/colors.dart'; +import 'package:ride_share/src/constants/image_strings.dart'; +import 'package:ride_share/src/repository/authentication_repository.dart'; + +import '../../../../constants/sizes.dart'; +import '../../../../constants/text_strings.dart'; +import '../../controllers/otp_controller.dart'; + +class ForgotPwdOTPScreen extends StatelessWidget { + const ForgotPwdOTPScreen({super.key, required this.phoneNo}); + + final String phoneNo; + + @override + Widget build(BuildContext context) { + var otpController = Get.put(OTPController()); + String otp = ""; + final size = MediaQuery.of(context).size; + return SafeArea( + child: Scaffold( + body: SingleChildScrollView( + child: Stack( + children: [ + Column( + children: [ + Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + colors: [ + Colors.orange, + Colors.deepOrange, + ], + ), + ), + height: size.height * 0.45, + alignment: Alignment.center, + child: Image( + alignment: Alignment.bottomCenter, + image: const AssetImage(tOTPScreenImage), + height: size.height * 0.5, + ), + ), + Container( + height: size.height * 0.45, + padding: const EdgeInsets.all(tDefaultSize), + child: Column( + children: [ + Text( + tOtpTitle, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headlineMedium, + textScaleFactor: 1.8, + ), + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: tSecondaryColor, + ), + height: 5, + width: size.width * 0.3, + ), + const SizedBox(height: 20.0), + Text( + "$tOtpMessage $phoneNo", + textAlign: TextAlign.center, + ), + const SizedBox(height: 20.0), + OtpTextField( + mainAxisAlignment: MainAxisAlignment.center, + numberOfFields: 6, + fillColor: Colors.black.withOpacity(0.1), + filled: true, + cursorColor: tSecondaryColor, + focusedBorderColor: tSecondaryColor, + onSubmit: (code) { + otp = code; + otpController.verifyForgotPwdOTP(otp, phoneNo); + }, + ), + const SizedBox( + height: 30.0, + ), + SizedBox( + width: size.width * 0.75, + child: ElevatedButton( + onPressed: () { + otpController.verifyForgotPwdOTP(otp, phoneNo); + }, + child: const Text( + "Next", + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 18, + letterSpacing: 1.5), + ), + ), + ), + const SizedBox( + height: tFormHeight - 10.0, + ), + TextButton( + onPressed: () { + AuthenticationRepository.instance.phoneAuthentication(phoneNo); + }, + child: Text.rich( + TextSpan( + text: "$tOtpNotSent ", + style: Theme.of(context).textTheme.bodyMedium, + children: const [ + TextSpan( + text: tResendOtp, + style: TextStyle( + color: tSecondaryColor, + decoration: TextDecoration.underline), + ) + ], + ), + ), + ), + ], + ), + ) + ], + ) + ], + ), + ) + ), + ); + } +} diff --git a/ride_share/lib/src/features/authentication/screens/forgot_pwd/forgot_pwd_phone_screen.dart b/ride_share/lib/src/features/authentication/screens/forgot_pwd/forgot_pwd_phone_screen.dart index 1e68cdc..1561c04 100644 --- a/ride_share/lib/src/features/authentication/screens/forgot_pwd/forgot_pwd_phone_screen.dart +++ b/ride_share/lib/src/features/authentication/screens/forgot_pwd/forgot_pwd_phone_screen.dart @@ -1,4 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:ride_share/src/features/authentication/screens/forgot_pwd/forgot_pwd_otp_screen.dart'; +import 'package:ride_share/src/repository/authentication_repository.dart'; import '../../../../common_widgets/form_header_widget.dart'; import '../../../../constants/colors.dart'; @@ -54,8 +57,9 @@ class _ForgotPasswordPhoneScreenState extends State { SizedBox( width: double.infinity, child: ElevatedButton( - onPressed: () { - + onPressed: () async{ + await AuthenticationRepository.instance.phoneAuthentication(controller.text.trim()); + Get.to(() => ForgotPwdOTPScreen(phoneNo: controller.text.trim())); }, child: const Text("Next") ), diff --git a/ride_share/lib/src/features/authentication/screens/forgot_pwd/otp_screen.dart b/ride_share/lib/src/features/authentication/screens/forgot_pwd/otp_screen.dart index bad4035..47f5448 100644 --- a/ride_share/lib/src/features/authentication/screens/forgot_pwd/otp_screen.dart +++ b/ride_share/lib/src/features/authentication/screens/forgot_pwd/otp_screen.dart @@ -1,150 +1,13 @@ -// import 'package:flutter/material.dart'; -// import 'package:flutter_otp_text_field/flutter_otp_text_field.dart'; -// import 'package:ride_share/src/constants/colors.dart'; -// import 'package:ride_share/src/constants/image_strings.dart'; -// import 'package:ride_share/src/utils/theme/widgets_themes/text_theme.dart'; -// import '../../../../constants/sizes.dart'; -// import '../../../../constants/text_strings.dart'; -// -// class OTPScreen extends StatefulWidget { -// const OTPScreen({super.key, required this.phoneNo}); -// -// final String phoneNo; -// -// @override -// State createState() => _OTPScreenState(); -// } -// -// class _OTPScreenState extends State { -// @override -// Widget build(BuildContext context) { -// final size = MediaQuery.of(context).size; -// var otp; -// return Scaffold( -// body: Stack( -// alignment: Alignment.center, -// clipBehavior: Clip.none, -// children: [ -// Positioned( -// child: Container( -// decoration: const BoxDecoration( -// gradient: LinearGradient( -// begin: Alignment.bottomCenter, -// end: Alignment.topCenter, -// colors: [ -// Colors.orange, -// Colors.deepOrange, -// ], -// )), -// height: size.height * 0.45, -// alignment: Alignment.center, -// ), -// ), -// Positioned( -// top: size.height * 0.5, -// child: Container( -// height: size.height - (size.height * 0.5), -// width: size.width, -// child: Padding( -// padding: EdgeInsets.only(left: 15, right: 15), -// child: Column( -// mainAxisAlignment: MainAxisAlignment.start, -// crossAxisAlignment: CrossAxisAlignment.center, -// children: [ -// // Text( -// // tOtpTitle, -// // textAlign: TextAlign.center, -// // style: Theme.of(context).textTheme.headlineMedium, -// // textScaleFactor: 1.8, -// // ), -// // Container( -// // decoration: BoxDecoration( -// // borderRadius: BorderRadius.circular(10), -// // color: tSecondaryColor, -// // ), -// // height: 5, -// // width: size.width * 0.3, -// // ), -// // const SizedBox(height: 20.0), -// // Text( -// // "$tOtpMessage $phoneNo", -// // textAlign: TextAlign.center, -// // ), -// // const SizedBox(height: 20.0), -// OtpTextField( -// mainAxisAlignment: MainAxisAlignment.center, -// numberOfFields: 6, -// fillColor: Colors.black.withOpacity(0.1), -// filled: true, -// keyboardType: TextInputType.number, -// onSubmit: (code) { -// otp = code; -// // OTPController.instance.verifyOTP(otp); -// }, -// ), -// // const SizedBox( -// // height: 30.0, -// // ), -// // SizedBox( -// // width: size.width * 0.75, -// // child: ElevatedButton( -// // onPressed: () { -// // // OTPController.instance.verifyOTP(otp); -// // }, -// // child: const Text( -// // "Next", -// // style: TextStyle( -// // fontWeight: FontWeight.w600, -// // fontSize: 18, -// // letterSpacing: 1.5), -// // ), -// // ), -// // ), -// // const SizedBox( -// // height: tFormHeight - 10.0, -// // ), -// // TextButton( -// // onPressed: () {}, -// // child: Text.rich(TextSpan( -// // text: "$tOtpNotSent ", -// // style: Theme.of(context).textTheme.bodyMedium, -// // children: const [ -// // TextSpan( -// // text: tResendOtp, -// // style: TextStyle( -// // color: tSecondaryColor, -// // decoration: TextDecoration.underline), -// // ) -// // ], -// // ), -// // ), -// // ), -// ], -// ), -// ), -// ), -// ), -// // Positioned( -// // top: 26, -// // child: Image( -// // image: const AssetImage(tOTPScreenImage), -// // height: size.height * 0.45, -// // ), -// // ), -// ], -// ), -// ); -// } -// } - import 'package:flutter/material.dart'; import 'package:flutter_otp_text_field/flutter_otp_text_field.dart'; -import 'package:google_fonts/google_fonts.dart'; +import 'package:get/get.dart'; import 'package:ride_share/src/constants/colors.dart'; import 'package:ride_share/src/constants/image_strings.dart'; +import 'package:ride_share/src/repository/authentication_repository.dart'; import '../../../../constants/sizes.dart'; import '../../../../constants/text_strings.dart'; +import '../../controllers/otp_controller.dart'; class OTPScreen extends StatelessWidget { const OTPScreen({super.key, required this.phoneNo}); @@ -153,8 +16,8 @@ class OTPScreen extends StatelessWidget { @override Widget build(BuildContext context) { - // var otpController = Get.put(OTPController()); - var otp; + var otpController = Get.put(OTPController()); + String otp = ""; final size = MediaQuery.of(context).size; return SafeArea( child: Scaffold( @@ -215,8 +78,8 @@ class OTPScreen extends StatelessWidget { cursorColor: tSecondaryColor, focusedBorderColor: tSecondaryColor, onSubmit: (code) { - // otp = code; - // OTPController.instance.verifyOTP(otp); + otp = code; + otpController.verifyOTP(otp); }, ), const SizedBox( @@ -226,7 +89,7 @@ class OTPScreen extends StatelessWidget { width: size.width * 0.75, child: ElevatedButton( onPressed: () { - // OTPController.instance.verifyOTP(otp); + otpController.verifyOTP(otp); }, child: const Text( "Next", @@ -241,7 +104,9 @@ class OTPScreen extends StatelessWidget { height: tFormHeight - 10.0, ), TextButton( - onPressed: () {}, + onPressed: () { + AuthenticationRepository.instance.phoneAuthentication(phoneNo); + }, child: Text.rich( TextSpan( text: "$tOtpNotSent ", @@ -264,7 +129,8 @@ class OTPScreen extends StatelessWidget { ) ], ), - )), + ) + ), ); } } diff --git a/ride_share/lib/src/features/authentication/screens/forgot_pwd/password_reset_screen.dart b/ride_share/lib/src/features/authentication/screens/forgot_pwd/password_reset_screen.dart new file mode 100644 index 0000000..22468da --- /dev/null +++ b/ride_share/lib/src/features/authentication/screens/forgot_pwd/password_reset_screen.dart @@ -0,0 +1,132 @@ +import 'package:dbcrypt/dbcrypt.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:ride_share/src/constants/colors.dart'; +import 'package:ride_share/src/constants/image_strings.dart'; +import 'package:ride_share/src/constants/text_strings.dart'; +import 'package:ride_share/src/features/authentication/controllers/otp_controller.dart'; +import 'package:ride_share/src/features/authentication/screens/login/login_screen.dart'; +import 'package:ride_share/src/models/user_model.dart'; +import 'package:ride_share/src/repository/authentication_repository.dart'; +import 'package:ride_share/src/repository/user_repository.dart'; + +import '../../../../common_widgets/form_header_widget.dart'; +import '../../../../constants/sizes.dart'; + +class PasswordResetScreen extends StatefulWidget { + const PasswordResetScreen({super.key, required this.phoneNo}); + + final String phoneNo; + + @override + State createState() => _PasswordResetScreenState(); +} + +class _PasswordResetScreenState extends State { + + @override + Widget build(BuildContext context) { + final controller = Get.put(OTPController()); + final _formKey = GlobalKey(); + + + return SafeArea( + child: Scaffold( + body: SingleChildScrollView( + child: Container( + padding: const EdgeInsets.all(tDefaultSize), + child: Column( + children: [ + const SizedBox( + height: tDefaultSize * 4, + ), + const FormHeaderWidget( + image: tResetPwdScreenImage, + title: tResetMailTitle, + subTitle: tViaEmailSubtitle, + heightBetween: 7.0, + imageHeight: 0.3, + lineWidth: 0.2, + ), + const SizedBox(height: tFormHeight), + + Form( + key: _formKey, + child: Column( + children: [ + TextFormField( + cursorColor: tSecondaryColor, + controller: controller.newPassword, + obscureText: true, + decoration: const InputDecoration( + label: Text(tPassword), + hintText: tPassword, + prefixIcon: Icon(Icons.password), + ), + ), + TextFormField( + cursorColor: tSecondaryColor, + controller: controller.confirmPassword, + obscureText: true, + decoration: const InputDecoration( + label: Text(tCFMPassword), + hintText: tCFMPassword, + prefixIcon: Icon(Icons.gpp_good_outlined), + ), + ), + const SizedBox(height: 35.0), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () async { + if (_formKey.currentState!.validate()) { + UserModel user = await UserRepository.instance.getUserDetails(widget.phoneNo); + // user.password = encryptPassword(controller.newPassword.text.trim()); + print(user.fullName); + user.password = controller.newPassword.text.trim(); + UserRepository.instance.updateUser(user); + Get.offAll(() => const LoginScreen()); + } + }, + child: const Text("Reset") + ), + ) + ], + ) + ) + ], + ), + ), + ), + ), + ); + } + + String encryptPassword(String pwd) { + return DBCrypt().hashpw(pwd, DBCrypt().gensalt()); + } + + +// Future passwordReset() async{ +// try{ +// await FirebaseAuth.instance.sendPasswordResetEmail(email: controller.text.trim()); +// showDialog( +// context: context, +// builder: (context){ +// return AlertDialog( +// content: Text("An email with a link to reset your password has been sent to your account"), +// ); +// }, +// ); +// }on FirebaseAuthException catch (e){ +// showDialog( +// context: context, +// builder: (context){ +// return AlertDialog(content: Text(e.message.toString()), +// ); +// }, +// ); +// } +// } +} \ No newline at end of file diff --git a/ride_share/lib/src/features/authentication/screens/login/login_form_widget.dart b/ride_share/lib/src/features/authentication/screens/login/login_form_widget.dart index 2d990a4..b5cde54 100644 --- a/ride_share/lib/src/features/authentication/screens/login/login_form_widget.dart +++ b/ride_share/lib/src/features/authentication/screens/login/login_form_widget.dart @@ -3,9 +3,12 @@ import 'package:get/get.dart'; import 'package:line_awesome_flutter/line_awesome_flutter.dart'; import 'package:ride_share/src/constants/colors.dart'; import 'package:ride_share/src/features/authentication/screens/forgot_pwd/forgot_password_bottom_modal.dart'; +import 'package:ride_share/src/features/home/screens/home_screen.dart'; +import 'package:ride_share/src/features/home/screens/new_home_screen.dart'; import '../../../../constants/sizes.dart'; import '../../../../constants/text_strings.dart'; +import '../../../../repository/authentication_repository.dart'; import '../../controllers/login_controller.dart'; class LoginForm extends StatefulWidget { @@ -35,17 +38,17 @@ class _LoginFormState extends State { children: [ TextFormField( cursorColor: tSecondaryColor, - controller: controller.email, + controller: controller.phone, validator: (value) { if (value == null || value.isEmpty) { - return '*Please enter an email address'; + return '*Please enter your phone number'; } return null; }, decoration: const InputDecoration( - prefixIcon: Icon(Icons.email_outlined), - labelText: tEmail, - hintText: tEmail, + prefixIcon: Icon(Icons.phone), + labelText: tPhoneNo, + hintText: tPhoneHintText, border: OutlineInputBorder()), ), const SizedBox( @@ -63,34 +66,36 @@ class _LoginFormState extends State { obscureText: obscurePassword, autocorrect: false, decoration: InputDecoration( - prefixIcon: const Icon(Icons.password_outlined), - labelText: tPassword, - hintText: tPassword, - border: const OutlineInputBorder(), - suffixIcon: IconButton( - icon: Icon(obscurePassword - ? LineAwesomeIcons.eye - : LineAwesomeIcons.eye_slash), - color: Colors.grey, - onPressed: () { - setState(() { - obscurePassword = !obscurePassword; - }); - }, - )), + prefixIcon: const Icon(Icons.password_outlined), + labelText: tPassword, + hintText: tPassword, + border: const OutlineInputBorder(), + suffixIcon: IconButton( + icon: Icon(obscurePassword + ? LineAwesomeIcons.eye + : LineAwesomeIcons.eye_slash), + color: Colors.grey, + onPressed: () { + setState(() { + obscurePassword = !obscurePassword; + }); + }, + ), + ), ), const SizedBox( height: tFormHeight, ), Visibility( - visible: invalidCredentials, - maintainSize: true, - maintainAnimation: true, - maintainState: true, - child: const Text( - "Invalid Credentials. Please try again.", - style: TextStyle(color: Colors.red), - )), + visible: invalidCredentials, + maintainSize: true, + maintainAnimation: true, + maintainState: true, + child: const Text( + "Invalid Credentials. Please try again.", + style: TextStyle(color: Colors.red), + ), + ), SizedBox( width: double.infinity, child: ElevatedButton( @@ -99,21 +104,28 @@ class _LoginFormState extends State { borderRadius: BorderRadius.circular(7.0))), ), onPressed: () async { - // if (_formKey.currentState!.validate()) { - // bool result = await AuthenticationRepository.instance - // .loginUserWithEmailAndPassword( - // controller.email.text.trim(), - // controller.password.text.trim()); - // if (result == true) { - // Get.offAll(() => const DashboardScreen()); - // } else { - // setState(() { - // invalidCredentials = true; - // }); - // } - // } + print("pressed"); + if (_formKey.currentState!.validate()) { + bool result = await AuthenticationRepository.instance + .loginUserWithPhoneAndPassword( + controller.phone.text.trim(), + controller.password.text.trim()); + if (result == true) { + Get.offAll(() => const NewHomeScreen()); + } else { + setState(() { + invalidCredentials = true; + }); + } + } }, - child: Text(tLogin.toUpperCase(), style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500, letterSpacing: 0.5))), + child: Text(tLogin.toUpperCase(), + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + letterSpacing: 0.5) + ) + ), ), Align( alignment: Alignment.centerRight, @@ -121,10 +133,15 @@ class _LoginFormState extends State { onPressed: () { ForgotPasswordModal.buildShowModalSheet(context); }, - child: const Text(tForgotPassword, style: TextStyle(color: tSecondaryColor),)), + child: const Text( + tForgotPassword, + style: TextStyle(color: tSecondaryColor), + ), + ), ), ], ), - )); + ), + ); } -} \ No newline at end of file +} diff --git a/ride_share/lib/src/features/authentication/screens/signup/signup_form_widget.dart b/ride_share/lib/src/features/authentication/screens/signup/signup_form_widget.dart index 4faeff0..62327dc 100644 --- a/ride_share/lib/src/features/authentication/screens/signup/signup_form_widget.dart +++ b/ride_share/lib/src/features/authentication/screens/signup/signup_form_widget.dart @@ -1,3 +1,4 @@ +import 'package:dbcrypt/dbcrypt.dart'; import 'package:email_validator/email_validator.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -6,8 +7,8 @@ import 'package:ride_share/src/constants/text_strings.dart'; import '../../../../constants/colors.dart'; import '../../../../constants/sizes.dart'; +import '../../../../models/user_model.dart'; import '../../controllers/signup_controller.dart'; -import '../forgot_pwd/otp_screen.dart'; class SignUpFormWidget extends StatefulWidget { const SignUpFormWidget({ @@ -20,152 +21,156 @@ class SignUpFormWidget extends StatefulWidget { class _SignUpFormWidgetState extends State { bool obscurePassword = true; - + bool obscureCfmPassword = true; @override Widget build(BuildContext context) { - final controller = Get.put(SignUpController()); final _formKey = GlobalKey(); return Container( padding: const EdgeInsets.symmetric(vertical: tFormHeight - 10), child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextFormField( - cursorColor: tSecondaryColor, - controller: controller.firstName, - decoration: const InputDecoration( - label: Text(tFullName), - prefixIcon: Icon(Icons.person_outline_outlined), - ), - validator: (value) { - if (value == null || value.isEmpty) { - return '*This field is required'; - } - return null; - }, + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextFormField( + cursorColor: tSecondaryColor, + controller: controller.fullName, + decoration: const InputDecoration( + label: Text(tFullName), + prefixIcon: Icon(Icons.person_outline_outlined), ), - const SizedBox(height: tFormHeight - 20), - TextFormField( - cursorColor: tSecondaryColor, - controller: controller.email, - decoration: const InputDecoration( - label: Text(tEmail), - prefixIcon: Icon(Icons.email_outlined), - ), - onFieldSubmitted: (val) { - validateEmail(val); - }, - validator: (email) { - bool result = validateEmail(email!); - if (result) { - return null; - } - return '*Invalid email address'; - }, + validator: (value) { + if (value == null || value.isEmpty) { + return '*This field is required'; + } + return null; + }, + ), + const SizedBox(height: tFormHeight - 20), + TextFormField( + cursorColor: tSecondaryColor, + controller: controller.email, + decoration: const InputDecoration( + label: Text(tEmail), + prefixIcon: Icon(Icons.email_outlined), ), - const SizedBox(height: tFormHeight - 20), - - TextFormField( - cursorColor: tSecondaryColor, - controller: controller.phoneNo, - decoration: const InputDecoration( - label: Text(tPhoneNo), - prefixIcon: Icon(Icons.phone), - ), - validator: (value) { - if (value == null || value.isEmpty) { - return '*Phone Number is required'; - } - return null; - }, - ), - - const SizedBox(height: tFormHeight - 20), - TextFormField( - cursorColor: tSecondaryColor, - controller: controller.password, - obscureText: obscurePassword, - autocorrect: false, - decoration: InputDecoration( - label: const Text(tPassword), - prefixIcon: const Icon(Icons.password_outlined), - suffixIcon: IconButton( - icon: Icon(obscurePassword - ? LineAwesomeIcons.eye - : LineAwesomeIcons.eye_slash), - color: Colors.grey, - onPressed: () { - setState(() { - obscurePassword = !obscurePassword; - }); - }, - ) - ), - validator: (value) { - if (value == null || value.isEmpty) { - return '*This field is required'; - } else if (value.isNotEmpty) { - bool result = validatePassword(value); - return result ? null : 'Please use a strong password'; - } + onFieldSubmitted: (val) { + validateEmail(val); + }, + validator: (email) { + bool result = validateEmail(email!); + if (result) { return null; - }, + } + return '*Invalid email address'; + }, + ), + const SizedBox(height: tFormHeight - 20), + TextFormField( + cursorColor: tSecondaryColor, + controller: controller.phoneNo, + decoration: const InputDecoration( + label: Text(tPhoneNo), + prefixIcon: Icon(Icons.phone), ), - const SizedBox(height: tFormHeight - 20), - TextFormField( - cursorColor: tSecondaryColor, - obscureText: obscurePassword, - autocorrect: false, - decoration: const InputDecoration( - label: Text(tCFMPassword), - prefixIcon: Icon(Icons.gpp_good_outlined), - ), - validator: (value) { - if (value == null || value.isEmpty) { - return '*Please Confirm Your Password'; - } else if (value != controller.password.text.trim()) { - return 'Passwords do not Match'; - } - return null; - }, + validator: (value) { + if (value == null || value.isEmpty) { + return '*Phone Number is required'; + } + return null; + }, + ), + const SizedBox(height: tFormHeight - 20), + TextFormField( + cursorColor: tSecondaryColor, + controller: controller.password, + obscureText: obscurePassword, + autocorrect: false, + decoration: InputDecoration( + label: const Text(tPassword), + prefixIcon: const Icon(Icons.password_outlined), + suffixIcon: IconButton( + icon: Icon(obscurePassword + ? LineAwesomeIcons.eye + : LineAwesomeIcons.eye_slash), + color: Colors.grey, + onPressed: () { + setState(() { + obscurePassword = !obscurePassword; + }); + }, + ) ), - const SizedBox(height: tFormHeight - 10), - SizedBox( - width: double.infinity, - child: ElevatedButton( - style: ButtonStyle( - shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(7.0))), - ), + validator: (value) { + if (value == null || value.isEmpty) { + return '*This field is required'; + } else if (value.isNotEmpty) { + bool result = validatePassword(value); + return result ? null : 'Please use a strong password'; + } + return null; + }, + ), + const SizedBox(height: tFormHeight - 20), + TextFormField( + cursorColor: tSecondaryColor, + obscureText: obscurePassword, + autocorrect: false, + enableInteractiveSelection: false, + decoration: InputDecoration( + label: const Text(tCFMPassword), + prefixIcon: const Icon(Icons.gpp_good_outlined), + suffixIcon: IconButton( + icon: Icon(obscureCfmPassword + ? LineAwesomeIcons.eye + : LineAwesomeIcons.eye_slash), + color: Colors.grey, onPressed: () { - Get.to(() => OTPScreen(phoneNo: controller.phoneNo.text.trim())); - // Get.to(() => const OTPScreen()); - // if (_formKey.currentState!.validate()) { - // SignUpController.instance.phoneAuthentication(controller.phoneNo.text.trim()); - // Get.to(() => const OTPScreen()); - // // var now = DateTime.now(); - // // var formatter = DateFormat('dd-MM-yyyy'); - // // String formattedDate = formatter.format(now); - // // - // // final user = UserModel( - // // email: controller.email.text.trim(), - // // password: controller.password.text.trim(), - // // fullName: controller.fullName.text.trim(), - // // phoneVerified: false, - // // joinedOn: formattedDate, - // // ); - // // - // // SignUpController.instance.createUser(user); - // } + setState(() { + obscureCfmPassword = !obscureCfmPassword; + }); }, - child: Text(tSignup.toUpperCase())), + ) ), - ], - )), + validator: (value) { + if (value == null || value.isEmpty) { + return '*Please Confirm Your Password'; + } else if (value != controller.password.text.trim()) { + return 'Passwords do not Match'; + } + return null; + }, + ), + const SizedBox(height: tFormHeight - 10), + SizedBox( + width: double.infinity, + child: ElevatedButton( + style: ButtonStyle( + shape: MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(7.0))), + ), + onPressed: () { + if (_formKey.currentState!.validate()) { + final user = UserModel( + email: controller.email.text.trim(), + password: controller.password.text.trim(), + // encryptPassword(controller.password.text.trim()), + fullName: controller.fullName.text.trim(), + emailVerified: false, + phoneNo: controller.phoneNo.text.trim(), + ); + + SignUpController.instance.createUser(user); + } + }, + child: Text(tSignup.toUpperCase())), + ), + ], + ), + ), ); } @@ -179,10 +184,14 @@ class _SignUpFormWidgetState extends State { } } + String encryptPassword(String pwd) { + return DBCrypt().hashpw(pwd, DBCrypt().gensalt()); + } + bool validatePassword(String password) { String pattern = r'^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[!@#\$&*~]).{8,}$'; RegExp regExp = RegExp(pattern); return regExp.hasMatch(password); } -} \ No newline at end of file +} diff --git a/ride_share/lib/src/features/authentication/screens/welcome/welcome_screen.dart b/ride_share/lib/src/features/authentication/screens/welcome/welcome_screen.dart index 2fbf39b..48a98a9 100644 --- a/ride_share/lib/src/features/authentication/screens/welcome/welcome_screen.dart +++ b/ride_share/lib/src/features/authentication/screens/welcome/welcome_screen.dart @@ -29,14 +29,14 @@ class WelcomeScreen extends StatelessWidget{ RichText( text: TextSpan( text: "Welcome to ", - style: TextStyle(fontFamily: GoogleFonts.montserrat().fontFamily, fontWeight: FontWeight.bold, fontSize: 30.0, color: isDarkMode ? tWhiteColor : tBlackColor,), + style: TextStyle(fontFamily: GoogleFonts.montserrat().fontFamily, fontWeight: FontWeight.bold, fontSize: 25.0, color: isDarkMode ? tWhiteColor : tBlackColor,), children: const [ TextSpan(text: "Ride"), TextSpan(text: "Share", style: TextStyle(color: tSecondaryColor)), ], ), ), - Text(tWelcomeSubtitle, style: Theme.of(context).textTheme.bodyLarge, textAlign: TextAlign.center,), + Text(tWelcomeSubtitle, style: Theme.of(context).textTheme.bodyMedium, textAlign: TextAlign.center,), ], ), SizedBox( diff --git a/ride_share/lib/src/features/home/controllers/map_controller.dart b/ride_share/lib/src/features/home/controllers/map_controller.dart new file mode 100644 index 0000000..7a982e1 --- /dev/null +++ b/ride_share/lib/src/features/home/controllers/map_controller.dart @@ -0,0 +1,26 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter_google_places/flutter_google_places.dart'; +import 'package:get/get.dart'; +import 'package:google_maps_webservice/places.dart'; + +import '../../../../auth/secrets.dart'; + +class MapController extends GetxController{ + Future showGoogleAutoComplete(BuildContext context) async { + Prediction? p = await PlacesAutocomplete.show( + offset: 0, + radius: 1000, + strictbounds: false, + region: "ke", + language: "en", + context: context, + mode: Mode.overlay, + apiKey: mapsAPIKey, + components: [Component(Component.country, "ke")], + types: [], + hint: "Search City", + ); + + return p; + } +} \ No newline at end of file diff --git a/ride_share/lib/src/features/home/controllers/profile_controller.dart b/ride_share/lib/src/features/home/controllers/profile_controller.dart new file mode 100644 index 0000000..b1aeeb0 --- /dev/null +++ b/ride_share/lib/src/features/home/controllers/profile_controller.dart @@ -0,0 +1,32 @@ +import 'package:flutter/foundation.dart'; +import 'package:get/get.dart'; +import 'package:ride_share/src/features/authentication/screens/login/login_screen.dart'; + +import '../../../models/user_model.dart'; +import '../../../repository/authentication_repository.dart'; +import '../../../repository/user_repository.dart'; + +class ProfileController extends GetxController { + static ProfileController get instance => Get.find(); + + final _authRepo = Get.put(AuthenticationRepository()); + final _userRepo = Get.put(UserRepository()); + + getUserData() { + final phone = _authRepo.firebaseUser.value?.phoneNumber; + + if (phone != null) { + if (kDebugMode) { + print(_authRepo.firebaseUser.value); + } + return _userRepo.getUserDetails(phone); + } else { + Get.snackbar("Error", "Login to continue"); + Get.to(() => const LoginScreen()); + } + } + + updateRecord(UserModel user) async{ + await _userRepo.updateUser(user); + } +} \ No newline at end of file diff --git a/ride_share/lib/src/features/home/controllers/ride_controller.dart b/ride_share/lib/src/features/home/controllers/ride_controller.dart new file mode 100644 index 0000000..7a797c5 --- /dev/null +++ b/ride_share/lib/src/features/home/controllers/ride_controller.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:ride_share/src/features/home/screens/home_screen.dart'; +import 'package:ride_share/src/features/home/screens/new_home_screen.dart'; +import 'package:ride_share/src/models/offered_ride_model.dart'; +import 'package:ride_share/src/repository/ride_repository.dart'; + +class RideController extends GetxController{ + static RideController get instance => Get.find(); + + final rideRepo = Get.put(RideRepository()); + + final vehicleMake = TextEditingController(); + final vehicleModel = TextEditingController(); + final numberPlate = TextEditingController(); + final vehicleColor = TextEditingController(); + final seatsAvailable = TextEditingController(); + + Future addRide(OfferedRideModel ride) async { + await rideRepo.addRide(ride); + Get.to(() => const NewHomeScreen()); + } +} \ No newline at end of file diff --git a/ride_share/lib/src/features/home/screens/home_screen.dart b/ride_share/lib/src/features/home/screens/home_screen.dart new file mode 100644 index 0000000..06a711f --- /dev/null +++ b/ride_share/lib/src/features/home/screens/home_screen.dart @@ -0,0 +1,197 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:ride_share/src/common_widgets/nav_drawer.dart'; +import 'package:ride_share/src/constants/text_strings.dart'; +import 'package:ride_share/src/features/home/screens/ride/offer_ride_screen.dart'; + +import '../../../constants/colors.dart'; + +List histories = [ + {'id': 1, 'name': 'Balozi Estate', 'city': 'Nairobi'}, + {'id': 2, 'name': 'Ridgeways Country Homes', 'city': 'Ridgeways Rd, Nairobi'}, + {'id': 3, 'name': 'Chalbi Court', 'city': 'Nairobi'} +]; + +class HomeScreen extends StatefulWidget { + const HomeScreen({super.key}); + + @override + State createState() => _HomeScreenState(); +} + +class _HomeScreenState extends State { + late GoogleMapController mapController; + + final LatLng _center = const LatLng(-1.3093299, 36.8099464); + + List options = [Text('Offer'), Text('Request')]; + + void _onMapCreated(GoogleMapController controller) { + mapController = controller; + } + + @override + Widget build(BuildContext context) { + final List _selectedOptions = [false, true]; + bool vertical = false; + + return MaterialApp( + debugShowCheckedModeBanner: false, + theme: ThemeData( + useMaterial3: true, + colorSchemeSeed: Colors.green[700], + ), + home: Scaffold( + drawer: NavDrawer(), + extendBodyBehindAppBar: true, + appBar: AppBar( + backgroundColor: Colors.transparent, + elevation: 0, + iconTheme: const IconThemeData(size: 45.0, weight: 7, fill: 1, color: Colors.black), + actions: [ + ElevatedButton(onPressed: (){Get.to(() => const OfferRideScreen());}, child: Text("Offer")), + // TextButton( + // onPressed: () { + // Get.to(() => const OfferRideScreen()); + // }, + // child: Row( + // children: [ + // Text("Offer"), + // ], + // ), + // ), + ], + ), + body: Stack( + children: [ + LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return SizedBox( + height: constraints.maxHeight / 2, + child: GoogleMap( + onMapCreated: _onMapCreated, + initialCameraPosition: CameraPosition( + target: _center, + zoom: 11.0, + ), + ), + ); + }, + ), + // Row( + // crossAxisAlignment: CrossAxisAlignment.center, + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // Container( + // padding: EdgeInsets.zero, + // decoration: BoxDecoration( + // color: Colors.white, + // border: Border.all(color: Colors.black, width: 1.0), + // borderRadius: BorderRadius.all(Radius.circular(5.0)), + // ), + // child: TextButton( + // onPressed: () { + // Get.to(() => const OfferRideScreen()); + // }, + // child: Row( + // children: [ + // Text("Offer"), + // ], + // ), + // ), + // ), + // ], + // ), + + // Positioned( + // top: 50, + // left: 10, + // child: Padding( + // padding: const EdgeInsets.all(7.0), + // child: Align( + // alignment: Alignment.topLeft, + // child: FloatingActionButton( + // onPressed: () => NavDrawer(), + // materialTapTargetSize: MaterialTapTargetSize.padded, + // backgroundColor: Colors.white, + // child: const Icon(Icons.menu, size: 30.0), + // ), + // ), + // ), + // ), + Align( + alignment: const Alignment(0.9, 0.75), + child: RawMaterialButton( + onPressed: () {}, + elevation: 2.0, + fillColor: Colors.white, + padding: const EdgeInsets.all(7.0), + shape: const CircleBorder(), + child: const Icon( + Icons.my_location, + size: 30.0, + ), + ), + ), + DraggableScrollableSheet( + initialChildSize: 0.5, + minChildSize: 0.5, + maxChildSize: 1, + snapSizes: const [0.5, 1], + snap: true, + builder: + (BuildContext context, ScrollController scrollController) { + return Container( + color: Colors.white, + child: ListView.builder( + physics: const ClampingScrollPhysics(), + controller: scrollController, + itemCount: histories.length, + itemBuilder: (BuildContext context, int index) { + final history = histories[index]; + if (index == 0) { + return Padding( + padding: const EdgeInsets.all(3), + child: Column( + children: [ + const SizedBox( + width: 50, + child: Divider( + thickness: 5, + ), + ), + TextFormField( + cursorColor: tSecondaryColor, + decoration: const InputDecoration( + hintText: tPickUpLocation, + border: OutlineInputBorder()), + ), + ], + ), + ); + } + return Card( + margin: EdgeInsets.zero, + color: Colors.white, + elevation: 0, + child: ListTile( + contentPadding: EdgeInsets.all(5), + onTap: () {}, + leading: Icon(Icons.access_time), + title: Text(history['name']), + subtitle: Text(history['city']), + ), + ); + }), + ); + }, + ), + ], + ), + ), + ); + } +} diff --git a/ride_share/lib/src/features/home/screens/new_home_screen.dart b/ride_share/lib/src/features/home/screens/new_home_screen.dart new file mode 100644 index 0000000..1ada33a --- /dev/null +++ b/ride_share/lib/src/features/home/screens/new_home_screen.dart @@ -0,0 +1,929 @@ +import 'package:flutter/material.dart'; + +import 'package:get/get.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:flutter/services.dart' show rootBundle; + +import 'package:geocoding/geocoding.dart' as geoCoding; +import 'package:google_maps_webservice/places.dart'; +import 'dart:ui' as ui; + +import 'package:ride_share/src/constants/colors.dart'; +import 'package:ride_share/src/constants/text_strings.dart'; +import 'package:ride_share/src/features/home/controllers/map_controller.dart'; +import 'package:ride_share/src/features/home/screens/profile/edit_profile.dart'; +import 'package:ride_share/src/features/home/screens/requests/ride_request.dart'; +import 'package:ride_share/src/features/home/screens/ride/ride_registration.dart'; +import 'package:ride_share/src/features/home/screens/schedule/user_schedule.dart'; +import 'package:ride_share/src/models/ride_request_model.dart'; +import 'package:ride_share/src/repository/ride_repository.dart'; + +import '../../../common_widgets/text_widget.dart'; +import '../../../constants/image_strings.dart'; +import '../../../models/offered_ride_model.dart'; + +class NewHomeScreen extends StatefulWidget { + const NewHomeScreen({Key? key}) : super(key: key); + + @override + State createState() => _NewHomeScreenState(); +} + +class CarInfo { + final String name; + final String model; + final double cash; + final String to; + + CarInfo(this.name, this.model, this.cash, this.to); +} + +var items = [ + CarInfo('Nathan Mbugua', "Toyota Mark X", 350, "Makini School"), + CarInfo('Nathan Mbugua', "Toyota Mark X", 350, "Makini School"), + CarInfo('Wayne Asava', "Toyota Vitz", 350, "Starthmore University"), +]; + +class _NewHomeScreenState extends State { + final rideRepository = Get.put(RideRepository()); + MapController mapController = Get.put(MapController()); + + late LatLng source; + late LatLng destination; + final Set polyline = {}; + Set markers = Set(); + + @override + void initState() { + super.initState(); + + // loadCustomMarker(); + } + + // String dropdownValue = '**** **** **** 8789'; + final CameraPosition _kGooglePlex = const CameraPosition( + target: LatLng(-1.3093299, 36.8099464), + zoom: 10, + ); + + GoogleMapController? myMapController; + + @override + Widget build(BuildContext context) { + + return Scaffold( + drawer: buildDrawer(), + appBar: AppBar( + backgroundColor: tSecondaryColor, + elevation: 0.0, + ), + // appBar: AppBar( + // automaticallyImplyLeading: true, + // centerTitle: true, + // title: const Text( + // 'Navigation Drawer', + // ), + // backgroundColor: const Color(0xff764abc), + // ), + body: Stack( + children: [ + Positioned( + top: 0, + left: 0, + right: 0, + bottom: 0, + child: GoogleMap( + markers: markers, + polylines: polyline, + zoomControlsEnabled: true, + onMapCreated: (GoogleMapController controller) { + myMapController = controller; + + // myMapController!.setMapStyle(_mapStyle); + }, + initialCameraPosition: _kGooglePlex, + ), + ), + buildProfileTile(), + buildTextField(), + showDestinationField ? buildTextFieldForDestination() : Container(), + buildCurrentLocationIcon(), + buildBottomSheet(), + ], + ), + ); + } + + Widget buildProfileTile() { + return Positioned( + top: 0, + left: 0, + right: 0, + child: + // Obx(() => authController.myUser.value.name == null + // ? Center( + // child: CircularProgressIndicator(), + // ) + // : + Container( + width: Get.width, + height: Get.width * 0.2, + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 0), + decoration: const BoxDecoration(color: Colors.white), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + RichText( + text: const TextSpan(children: [ + TextSpan( + text: 'Welcome, ', + style: TextStyle(color: Colors.black, fontSize: 14)), + TextSpan( + text: "Nathan Mbugua", + style: TextStyle( + color: tSecondaryColor, + fontSize: 16, + fontWeight: FontWeight.bold)), + ]), + ), + ], + ) + ], + ), + ), + ); + } + + TextEditingController sourceController = TextEditingController(); + TextEditingController destinationController = TextEditingController(); + + bool showDestinationField = false; + + Widget buildTextField() { + return Positioned( + top: 80, + left: 20, + right: 20, + child: Container( + width: Get.width, + height: 50, + padding: const EdgeInsets.only(left: 15), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + spreadRadius: 4, + blurRadius: 10) + ], + borderRadius: BorderRadius.circular(8)), + child: TextFormField( + controller: sourceController, + readOnly: true, + onTap: () async { + Prediction? p = await mapController.showGoogleAutoComplete(context); + + String selectedPlace = p!.description!; + + sourceController.text = selectedPlace; + + List locations = + await geoCoding.locationFromAddress(selectedPlace); + + source = + LatLng(locations.first.latitude, locations.first.longitude); + + markers.add(Marker( + markerId: MarkerId(selectedPlace), + infoWindow: InfoWindow( + title: 'Destination: $selectedPlace', + ), + position: source, + // icon: BitmapDescriptor.fromBytes(markIcons), + )); + + myMapController!.animateCamera(CameraUpdate.newCameraPosition( + CameraPosition(target: source, zoom: 14) + //17 is new zoom level + )); + + // TODO build driver bottom sheet + buildRideConfirmationSheet(); + + // setState(() { + // showDestinationField = true; + // }); + }, + style: GoogleFonts.poppins( + fontSize: 15, fontWeight: FontWeight.bold, color: Colors.black54), + decoration: InputDecoration( + hintText: 'Where to?', + hintStyle: GoogleFonts.poppins( + fontSize: 14, + fontWeight: FontWeight.w400, + color: Colors.black54, + fontStyle: FontStyle.italic), + suffixIcon: const Padding( + padding: EdgeInsets.only(left: 10), + child: Icon( + Icons.search, + color: Colors.black, + ), + ), + border: InputBorder.none, + ), + ), + ), + ); + } + + Widget buildTextFieldForDestination() { + return Positioned( + top: 150, + left: 20, + right: 20, + child: Container( + width: Get.width, + height: 50, + padding: const EdgeInsets.only(left: 15), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + spreadRadius: 4, + blurRadius: 10) + ], + borderRadius: BorderRadius.circular(8)), + child: TextFormField( + controller: destinationController, + readOnly: true, + onTap: () async { + // buildSourceSheet(); + }, + style: GoogleFonts.poppins( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + decoration: InputDecoration( + hintText: 'From:', + hintStyle: GoogleFonts.poppins( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + suffixIcon: const Padding( + padding: EdgeInsets.only(left: 10), + child: Icon( + Icons.search, + ), + ), + border: InputBorder.none, + ), + ), + ), + ); + } + + Widget buildCurrentLocationIcon() { + return const Align( + alignment: Alignment.bottomRight, + child: Padding( + padding: EdgeInsets.only(bottom: 30, right: 8), + child: CircleAvatar( + radius: 20, + backgroundColor: Colors.white, + child: Icon( + Icons.my_location, + color: Colors.black87, + ), + ), + ), + ); + } + + Widget buildBottomSheet() { + return Align( + alignment: Alignment.bottomCenter, + child: Container( + width: Get.width, + height: 25, + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + spreadRadius: 4, + blurRadius: 10) + ], + borderRadius: const BorderRadius.only( + topRight: Radius.circular(12), topLeft: Radius.circular(12))), + child: Center( + child: Container( + width: Get.width * 0.3, + height: 4, + decoration: const BoxDecoration( + color: Colors.black45, + borderRadius: BorderRadius.only( + topRight: Radius.circular(12), + topLeft: Radius.circular(12), + bottomLeft: Radius.circular(12), + bottomRight: Radius.circular(12)), + ), + ), + ), + ), + ); + } + + buildDrawerItem( + {required String title, + required Function onPressed, + required Icon icon, + Color color = Colors.black, + double fontSize = 20, + FontWeight fontWeight = FontWeight.w700, + double height = 45, + bool isVisible = false}) { + return SizedBox( + height: height, + child: ListTile( + leading: icon, + onTap: () => onPressed(), + title: Row( + children: [ + Text( + title, + ), + const SizedBox( + width: 5, + ), + isVisible + ? CircleAvatar( + backgroundColor: tSecondaryColor, + radius: 15, + child: Text( + '1', + style: GoogleFonts.poppins(color: Colors.white), + ), + ) + : Container() + ], + ), + ), + ); + } + + buildDrawer() { + return Drawer( + child: Column( + children: [ + InkWell( + onTap: () { + Get.to(() => EditProfileScreen()); + }, + child: SizedBox( + height: 150, + child: DrawerHeader( + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Container( + width: 80, + height: 80, + decoration: const BoxDecoration( + shape: BoxShape.circle, + image: DecorationImage( + image: AssetImage(tProfilePic), fit: BoxFit.fill) + // : DecorationImage( + // image: NetworkImage( + // authController.myUser.value.image!), + // fit: BoxFit.fill) + ), + ), + const SizedBox( + width: 10, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Good Morning, ', + style: GoogleFonts.poppins( + color: Colors.black.withOpacity(0.28), + fontSize: 14)), + Text( + // authController.myUser.value.name == null + // ? "Mark" + // : authController.myUser.value.name!, + "Nathan", + style: GoogleFonts.poppins( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.black), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ) + ], + ), + ) + ], + )), + ), + ), + const SizedBox( + height: 20, + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 30), + child: Column( + children: [ + // buildDrawerItem(title: 'Payment History', onPressed: () => Get.to(()=> PaymentScreen())), + buildDrawerItem( + title: tPayment, + onPressed: () {}, + icon: const Icon(Icons.wallet_outlined)), + buildDrawerItem( + title: tMySchedule, + onPressed: () { + Get.to(() => UserSchedule()); + }, + icon: const Icon(Icons.calendar_month)), + buildDrawerItem( + title: tTripHistory, + onPressed: () {}, + icon: const Icon(Icons.history)), + buildDrawerItem( + title: tRideRequests, + onPressed: () { + Get.to(() => RideRequests()); + }, + icon: const Icon(Icons.people_alt_outlined)), + + const Divider( + height: 2, + thickness: 1, + color: Colors.grey, + ), + + buildDrawerItem( + title: "Offer a Ride", + onPressed: () { + Get.to(() => const RideRegistrationTemplate()); + }, + icon: const Icon(Icons.drive_eta)), + + const Divider( + height: 2, + thickness: 1, + color: Colors.grey, + ), + + buildDrawerItem( + title: tSupport, + onPressed: () {}, + icon: const Icon(Icons.contact_support_outlined)), + buildDrawerItem( + title: tAbout, + onPressed: () {}, + icon: const Icon(Icons.info_outlined)), + ], + ), + ), + ], + ), + ); + } + +// late Uint8List markIcons; + +// loadCustomMarker() async { +// markIcons = await loadAsset('assets/dest_marker.png', 100); +// } + +// Future loadAsset(String path, int width) async { +// ByteData data = await rootBundle.load(path); +// ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List(), +// targetHeight: width); +// ui.FrameInfo fi = await codec.getNextFrame(); +// return (await fi.image.toByteData(format: ui.ImageByteFormat.png))! +// .buffer +// .asUint8List(); +// } + +// void drawPolyline(String placeId) { +// _polyline.clear(); +// _polyline.add(Polyline( +// polylineId: PolylineId(placeId), +// visible: true, +// points: [source, destination], +// color: AppColors.greenColor, +// width: 5, +// )); +// } + + void buildDestinationSheet() { + Get.bottomSheet(Container( + width: Get.width, + height: Get.height * 0.5, + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + decoration: const BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(8), topRight: Radius.circular(8)), + color: Colors.white), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const SizedBox( + height: 10, + ), + const Text( + "Select Your Location", + style: TextStyle( + color: Colors.black, fontSize: 20, fontWeight: FontWeight.bold), + ), + const SizedBox( + height: 20, + ), + const Text( + "Home Address", + style: TextStyle( + color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold), + ), + const SizedBox( + height: 10, + ), + InkWell( + onTap: () async { + Get.back(); + // source = authController.myUser.value.homeAddress!; + destination = LatLng(-1.22233, 36.9187); + // sourceController.text = authController.myUser.value.hAddress!; + destinationController.text = "Home"; + + if (markers.length >= 2) { + markers.remove(markers.last); + } + markers.add(Marker( + // markerId: MarkerId(authController.myUser.value.hAddress!), + markerId: MarkerId("Home"), + infoWindow: InfoWindow( + // title: 'Source: ${authController.myUser.value.hAddress!}', + title: 'Source: Home}', + ), + position: destination)); + + // await getPolylines(source, destination); + + // drawPolyline(place); + + myMapController!.animateCamera(CameraUpdate.newCameraPosition( + CameraPosition(target: destination, zoom: 14))); + setState(() {}); + + buildRideConfirmationSheet(); + }, + child: Container( + width: Get.width, + height: 50, + padding: EdgeInsets.symmetric(horizontal: 10), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.04), + spreadRadius: 4, + blurRadius: 10) + ]), + child: Row( + children: const [ + Text( + // authController.myUser.value.hAddress!, + "Home", + style: TextStyle( + color: Colors.black, + fontSize: 12, + fontWeight: FontWeight.w600), + textAlign: TextAlign.start, + ), + ], + ), + ), + ), + const SizedBox( + height: 20, + ), + const Text( + "Business Address", + style: TextStyle( + color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold), + ), + const SizedBox( + height: 10, + ), + InkWell( + onTap: () async { + Get.back(); + // source = authController.myUser.value.bussinessAddres!; + destination = LatLng(-1.23434, 34.272753); + // sourceController.text = authController.myUser.value.bAddress!; + destinationController.text = "Home"; + + if (markers.length >= 2) { + markers.remove(markers.last); + } + markers.add(Marker( + // markerId: MarkerId(authController.myUser.value.bAddress!), + markerId: MarkerId("hnb"), + infoWindow: InfoWindow( + // title: 'Source: ${authController.myUser.value.bAddress!}', + title: 'Source: ', + ), + position: destination)); + + // await getPolylines(source, destination); + + // drawPolyline(place); + + myMapController!.animateCamera(CameraUpdate.newCameraPosition( + CameraPosition(target: destination, zoom: 14))); + setState(() {}); + + buildRideConfirmationSheet(); + }, + child: Container( + width: Get.width, + height: 50, + padding: EdgeInsets.symmetric(horizontal: 10), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.04), + spreadRadius: 4, + blurRadius: 10) + ]), + child: Row( + children: [ + Text( + // authController.myUser.value.bAddress!, + "HOME", + style: const TextStyle( + color: Colors.black, + fontSize: 12, + fontWeight: FontWeight.w600), + textAlign: TextAlign.start, + ), + ], + ), + ), + ), + const SizedBox( + height: 20, + ), + InkWell( + onTap: () async { + Get.back(); + Prediction? p = + await mapController.showGoogleAutoComplete(context); + + String place = p!.description!; + + destinationController.text = place; + + // source = await authController.buildLatLngFromAddress(place); + + if (markers.length >= 2) { + markers.remove(markers.last); + } + markers.add(Marker( + markerId: MarkerId(place), + infoWindow: InfoWindow( + title: 'Source: $place', + ), + position: destination)); + + // await getPolylines(source, destination); + + // drawPolyline(place); + + myMapController!.animateCamera(CameraUpdate.newCameraPosition( + CameraPosition(target: destination, zoom: 14))); + setState(() {}); + buildRideConfirmationSheet(); + }, + child: Container( + width: Get.width, + height: 50, + padding: const EdgeInsets.symmetric(horizontal: 10), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.04), + spreadRadius: 4, + blurRadius: 10) + ]), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Text( + "Search for Address", + style: TextStyle( + color: Colors.black, + fontSize: 12, + fontWeight: FontWeight.w600), + textAlign: TextAlign.start, + ), + ], + ), + ), + ), + ], + ), + )); + } + + buildRideConfirmationSheet() { + Get.bottomSheet(Container( + width: Get.width, + height: Get.height * 0.5, + padding: EdgeInsets.only(left: 20), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topRight: Radius.circular(12), topLeft: Radius.circular(12)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 10, + ), + Center( + child: Container( + width: Get.width * 0.2, + height: 8, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), color: Colors.grey), + ), + ), + const SizedBox( + height: 20, + ), + textWidget( + text: 'We found the following matches', + fontSize: 18, + fontWeight: FontWeight.bold), + textWidget( + text: 'Select an option:', + fontSize: 16, + fontWeight: FontWeight.normal), + const SizedBox( + height: 20, + ), + buildDriversList(), + ], + ), + )); + } + + int selectedRide = 0; + + buildDriversList() { + return Container( + height: 250, + width: Get.width, + // child: FutureBuilder>( + // future: rideRepository.allRides(), + // builder: (context, snapshot) { + // if (snapshot.connectionState == ConnectionState.done) { + // if (snapshot.hasData) { + // return ListView.builder( + // itemBuilder: (ctx, i) { + // return InkWell( + // onTap: () {}, + // child: buildDriverCard(snapshot.data!, i), + // ); + // }, + // itemCount: snapshot.data!.length, + // scrollDirection: Axis.horizontal, + // ); + // } + // } + // return Text("no data"); + // }, + // ), + + child: StatefulBuilder(builder: (context, set) { + return ListView.builder( + itemBuilder: (ctx, i) { + return InkWell( + onTap: () { + set(() { + selectedRide = i; + }); + }, + child: buildDriverCard(selectedRide == i), + ); + }, + itemCount: 5, + scrollDirection: Axis.horizontal, + ); + } + ), + ); + } + + buildDriverCard(bool selected) { + return Container( + margin: EdgeInsets.only(right: 8, left: 8, top: 4, bottom: 4), + height: 170, + width: 235, + decoration: BoxDecoration(boxShadow: const [ + BoxShadow( + color: tSecondaryColor, + offset: Offset(0, 5), + blurRadius: 5, + spreadRadius: 1) + ], borderRadius: BorderRadius.circular(12), color: tSecondaryColor), + child: Stack( + children: [ + Container( + padding: EdgeInsets.only(left: 10, top: 10, bottom: 10, right: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + textWidget( + text:"Nathan Mbugua", + color: Colors.white, + fontWeight: FontWeight.w700), + textWidget( + text: + "Toyota Mark X", + color: Colors.white, + fontWeight: FontWeight.w500), + SizedBox( + child: Row( + children: [ + Column( + children: [ + Text("Days"), + Text("Mon, Tue"), + ], + ), + Column( + children: [ + Text("Seats"), + Text("4"), + ], + ), + Column( + children: [ + Text("Pay"), + Text("350"), + ], + ), + ], + ), + ), + ElevatedButton( + onPressed: () { + // final rideRequest = RideRequestModel( + // rideId: rides[index].id!, + // ownerId: rides[index].userId, + // requestedByUser: rides[index].userId, + // pickupLocation: 'Strathmore University, Ole Sangale Rd', + // seatsAvailable: rides[index].seatsAvailable, + // similarityScore: 1.0, + // acceptedStatus: false); + // await rideRepository.makeRideRequest(rideRequest); + // TODO get to success page or show modal and return + + }, + child: Text("REQUEST")) + ], + ), + ), + // ElevatedButton(onPressed: () {}, child: Text("Request")) + // Positioned( + // right: -20, + // top: 0, + // bottom: 0, + // child: Image.asset('assets/Mask Group 2.png')) + ], + ), + ); + } +} diff --git a/ride_share/lib/src/features/home/screens/profile/edit_profile.dart b/ride_share/lib/src/features/home/screens/profile/edit_profile.dart new file mode 100644 index 0000000..42c2fbb --- /dev/null +++ b/ride_share/lib/src/features/home/screens/profile/edit_profile.dart @@ -0,0 +1,185 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:line_awesome_flutter/line_awesome_flutter.dart'; +import 'package:local_auth/local_auth.dart'; + +import '../../../../constants/colors.dart'; +import '../../../../constants/image_strings.dart'; +import '../../../../constants/sizes.dart'; +import '../../../../constants/text_strings.dart'; +import '../../../../models/user_model.dart'; +import '../../../../repository/authentication_repository.dart'; +import '../../controllers/profile_controller.dart'; + +class EditProfileScreen extends StatelessWidget { + EditProfileScreen({super.key}); + + final _authRepo = Get.put(AuthenticationRepository()); + + late final LocalAuthentication auth; + + bool _supportState = false; + + @override + Widget build(BuildContext context) { + final controller = Get.put(ProfileController()); + var isDark = MediaQuery.of(context).platformBrightness == Brightness.dark; + + return Scaffold( + appBar: AppBar( + leading: IconButton( + onPressed: () => Get.back(), + icon: const Icon(LineAwesomeIcons.angle_left)), + title: Text(tEditProfile, + style: Theme.of(context).textTheme.headlineMedium), + ), + body: SingleChildScrollView( + child: Container( + padding: const EdgeInsets.all(tDefaultSize), + child: FutureBuilder( + future: controller.getUserData(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.hasData) { + UserModel userModel = snapshot.data as UserModel; + + final fullName = + TextEditingController(text: userModel.fullName); + final email = TextEditingController(text: userModel.email); + final phoneNo = + TextEditingController(text: userModel.phoneNo); + final password = + TextEditingController(text: userModel.password); + + return Column( + children: [ + Stack( + children: [ + SizedBox( + width: 120, + height: 120, + child: ClipRRect( + borderRadius: BorderRadius.circular(100), + child: + const Image(image: AssetImage(tProfilePic)), + ), + ), + Positioned( + bottom: 0, + right: 0, + child: Container( + width: 35, + height: 35, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100), + color: tSecondaryColor, + ), + child: const Icon(LineAwesomeIcons.camera, + size: 20.0, color: Colors.black), + ), + ) + ], + ), + const SizedBox(height: 50), + Form( + child: Column( + children: [ + TextFormField( + controller: fullName, + decoration: const InputDecoration( + label: Text(tFullName), + prefixIcon: Icon(LineAwesomeIcons.user), + ), + ), + const SizedBox(height: tFormHeight - 20), + TextFormField( + controller: email, + decoration: const InputDecoration( + label: Text(tEmail), + prefixIcon: Icon(LineAwesomeIcons.envelope_1), + ), + ), + const SizedBox(height: tFormHeight - 20), + TextFormField( + controller: phoneNo, + decoration: const InputDecoration( + label: Text(tPhoneNo), + prefixIcon: Icon(LineAwesomeIcons.phone), + ), + ), + const SizedBox(height: tFormHeight - 20), + TextFormField( + controller: password, + obscureText: true, + decoration: InputDecoration( + label: const Text(tPassword), + prefixIcon: + const Icon(Icons.fingerprint_outlined), + suffixIcon: IconButton( + icon: + const Icon(LineAwesomeIcons.eye_slash), + onPressed: () {})), + ), + const SizedBox(height: tFormHeight), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () async { + // if(await _authenticate()){ + final userData = UserModel( + email: email.text.trim(), + password: password.text.trim(), + fullName: fullName.text.trim(), + phoneNo: phoneNo.text.trim(), + emailVerified: _authRepo + .firebaseUser.value?.emailVerified, + ); + + await controller.updateRecord(userData); + // } + }, + style: ElevatedButton.styleFrom( + backgroundColor: tPrimaryColor, + side: BorderSide.none, + shape: const StadiumBorder()), + child: const Text(tEditProfile, + style: TextStyle(color: tDarkColor)), + ), + ), + const SizedBox(height: tFormHeight), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () {}, + style: ElevatedButton.styleFrom( + backgroundColor: + Colors.redAccent.withOpacity(0.1), + elevation: 0, + foregroundColor: Colors.red, + shape: const StadiumBorder(), + side: BorderSide.none), + child: const Text(tDeleteAcc), + ), + ], + ) + ], + )) + ], + ); + } else if (snapshot.hasError) { + return Center(child: Text(snapshot.error.toString())); + } else { + return const Center(child: Text("Something went wrong")); + } + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ), + ), + ), + ); + } +} diff --git a/ride_share/lib/src/features/home/screens/profile/profile_screen.dart b/ride_share/lib/src/features/home/screens/profile/profile_screen.dart new file mode 100644 index 0000000..257207f --- /dev/null +++ b/ride_share/lib/src/features/home/screens/profile/profile_screen.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:line_awesome_flutter/line_awesome_flutter.dart'; +import 'package:ride_share/src/features/home/screens/profile/edit_profile.dart'; + +import '../../../../constants/colors.dart'; +import '../../../../constants/image_strings.dart'; +import '../../../../constants/sizes.dart'; +import '../../../../constants/text_strings.dart'; + +class ProfileScreen extends StatelessWidget{ + const ProfileScreen({super.key}); + + @override + Widget build(BuildContext context) { + var isDark = MediaQuery.of(context).platformBrightness == Brightness.dark; + return Scaffold( + appBar: AppBar( + leading: IconButton( + onPressed: () => Get.back(), + icon: const Icon(LineAwesomeIcons.angle_left)), + title: + Text(tProfile, style: Theme.of(context).textTheme.headlineMedium), + actions: [ + IconButton( + onPressed: () {}, + icon: Icon(isDark ? LineAwesomeIcons.sun : LineAwesomeIcons.moon)) + ], + ), + body: SingleChildScrollView( + child: Container( + padding: const EdgeInsets.all(0), + child: Column( + children: [ + Stack( + children: [ + SizedBox( + width: 120, + height: 120, + child: ClipRRect( + borderRadius: BorderRadius.circular(100), + child: const Image(image: AssetImage(tProfilePic)), + ), + ), + Positioned( + bottom: 0, + right: 0, + child: Container( + width: 35, + height: 35, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100), + color: tSecondaryColor, + ), + child: const Icon(LineAwesomeIcons.alternate_pencil, + size: 20.0, color: Colors.black), + ), + ) + ], + ), + const SizedBox(height: 10), + Text("Nathan kjfjg", + style: Theme.of(context).textTheme.headlineMedium), + Text("memem@hhhg", + style: Theme.of(context).textTheme.bodyMedium), + const SizedBox(height: 20), + SizedBox( + width: 200, + child: ElevatedButton( + onPressed: () => Get.to(() => EditProfileScreen()), + // onPressed: () {}, + style: ElevatedButton.styleFrom( + backgroundColor: tSecondaryColor, + side: BorderSide.none, + shape: const StadiumBorder()), + child: const Text(tEditProfile, + style: TextStyle(color: tDarkColor)), + ), + ), + const SizedBox(height: 30), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(Icons.mail_outline_outlined, color: Colors.grey), + Text("mmn@hgh"), + ElevatedButton( + onPressed: () {}, + style: ElevatedButton.styleFrom( + backgroundColor: tSecondaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20) + ) + ), + child: Text(tVerify) + ) + ], + ), + const Divider( + height: 3, + thickness: 3, + color: Colors.grey, + ), + const SizedBox(height: 10), + Text(tLocations, style: TextStyle(fontSize: 20),), + ListTile( + leading: Icon(Icons.home_filled, color: Colors.grey), + title: Text(tAddHome), + onTap: () => {}, + ), + ListTile( + leading: Icon(Icons.location_on_outlined, color: Colors.grey,), + title: Text(tAddPickup), + onTap: () => {}, + ), + const Divider( + height: 3, + thickness: 3, + color: Colors.grey, + ), + const SizedBox(height: 10), + Text(tLanguage, style: TextStyle(fontSize: 20),), + Text(tLanguageUK, style: TextStyle(fontSize: 17),), + const SizedBox(height: 10), + const Divider( + height: 3, + thickness: 3, + color: Colors.grey, + ), + ListTile( + leading: Icon(Icons.exit_to_app, color: Colors.grey,), + title: Text(tLogout), + onTap: () => {}, + ), + ListTile( + leading: Icon(Icons.delete_outline_outlined, color: Colors.red,), + title: Text(tAddPickup), + onTap: () => {}, + ), + ], + ), + ), + ), + ); + } + +} \ No newline at end of file diff --git a/ride_share/lib/src/features/home/screens/requests/live_location.dart b/ride_share/lib/src/features/home/screens/requests/live_location.dart new file mode 100644 index 0000000..75196a6 --- /dev/null +++ b/ride_share/lib/src/features/home/screens/requests/live_location.dart @@ -0,0 +1,122 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_polyline_points/flutter_polyline_points.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:location/location.dart'; +import 'package:ride_share/src/constants/colors.dart'; +import 'package:ride_share/src/constants/image_strings.dart'; + +import '../../../../../auth/secrets.dart'; + +class LiveTrackingPage extends StatefulWidget { + const LiveTrackingPage({Key? key}) : super(key: key); + + @override + State createState() => LiveTrackingPageState(); +} + +class LiveTrackingPageState extends State { + final Completer _controller = Completer(); + + static const LatLng sourceLocation = LatLng(1.3093299,36.8099464); + static const LatLng destination = LatLng(-1.2987826,36.7606058); + static const LatLng currentLocation = LatLng(1.3093299,36.8099464); + + + List polylineCoordinates = []; + + BitmapDescriptor sourceIcon = BitmapDescriptor.defaultMarker; + BitmapDescriptor destinationIcon = BitmapDescriptor.defaultMarker; + BitmapDescriptor currentLocationIcon = BitmapDescriptor.defaultMarker; + + void getCurrentLocation() async { + + } + + void getPolyPoints() async { + PolylinePoints polylinePoints = PolylinePoints(); + PolylineResult result = await polylinePoints.getRouteBetweenCoordinates( + mapsAPIKey, + PointLatLng(sourceLocation.latitude, sourceLocation.longitude), + PointLatLng(destination.latitude, destination.longitude), + ); + + if (result.points.isNotEmpty) { + result.points.forEach( + (PointLatLng point) => + polylineCoordinates.add(LatLng(point.latitude, point.longitude)), + ); + setState(() {}); + } + } + + void setCustomMarkerIcons() { + BitmapDescriptor.fromAssetImage( + ImageConfiguration.empty, tpinsource) + .then( + (icon) { + sourceIcon = icon; + }, + ); + + BitmapDescriptor.fromAssetImage( + ImageConfiguration.empty, tpindest) + .then( + (icon) { + destinationIcon = icon; + }, + ); + + BitmapDescriptor.fromAssetImage(ImageConfiguration.empty, tbadge) + .then( + (icon) { + currentLocationIcon = icon; + }, + ); + } + + @override + void initState() { + setCustomMarkerIcons();; + getPolyPoints(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text( + "Live Tracking", + style: TextStyle(color: Colors.black, fontSize: 16), + ), + ), + body: GoogleMap( + initialCameraPosition: CameraPosition( + target: LatLng( + currentLocation!.latitude!, currentLocation!.longitude!), + zoom: 13.5), + polylines: { + Polyline( + polylineId: const PolylineId("route"), + points: polylineCoordinates, + color: tSecondaryColor, + width: 6, + ), + }, + markers: { + const Marker( + markerId: MarkerId("source"), position: sourceLocation), + const Marker( + markerId: MarkerId("destination"), position: destination), + const Marker( + markerId: MarkerId("current"), position: currentLocation), + }, + onMapCreated: (mapController) { + _controller.complete(mapController); + }, + ), + ); + } +} \ No newline at end of file diff --git a/ride_share/lib/src/features/home/screens/requests/ride_request.dart b/ride_share/lib/src/features/home/screens/requests/ride_request.dart new file mode 100644 index 0000000..5497599 --- /dev/null +++ b/ride_share/lib/src/features/home/screens/requests/ride_request.dart @@ -0,0 +1,113 @@ +import 'dart:ui' as ui; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:ride_share/src/constants/colors.dart'; +import 'package:ride_share/src/constants/image_strings.dart'; +import 'package:ride_share/src/features/home/screens/requests/live_location.dart'; +import 'package:ride_share/src/models/ride_request_model.dart'; +import 'package:ride_share/src/models/user_model.dart'; +import 'package:ride_share/src/repository/ride_repository.dart'; +import 'package:ride_share/src/repository/user_repository.dart'; + +class RideRequests extends StatefulWidget { + @override + _RideRequestsState createState() => _RideRequestsState(); +} + +class _RideRequestsState extends State { + RideRepository rideRepository = Get.put(RideRepository.instance); + final double _borderRadius = 24; + // UserModel user = + // UserRepository.instance.getUserDetails("+254706446072") as UserModel; + + var items = [ + PlaceInfo('Mary Jane', 4.4, 'Chalbi Court', 350), + PlaceInfo('John Doe', 4.0, 'Riverside', 200), + PlaceInfo('Mary Jane', 4.7, 'Junction Mall', 230), + PlaceInfo('Mary Jane', 5.0, 'Lavington Mall', 150), + ]; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Ride Requests'), + backgroundColor: tSecondaryColor, + ), + body: ListView.builder( + shrinkWrap: true, + itemCount: 4, + itemBuilder: (context, index) { + return Column( + children: [ + ListTile( + tileColor: Colors.white, + leading: const Icon(Icons.person), + title: Text(items[index].name), + trailing: ElevatedButton(onPressed: () { + Get.to(() => const LiveTrackingPage()); + }, child: Text("Pickup"),), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(items[index].location), + Text("Paying 350"), + ], + ), + ) + ], + ); + }, + ), + ); + } +} +// child: Container( +// width: 300, +// height: 200, +// padding: new EdgeInsets.all(10.0), +// child: Card( +// shape: RoundedRectangleBorder( +// borderRadius: BorderRadius.circular(15.0), +// ), +// color: Colors.red, +// elevation: 10, +// child: Column( +// mainAxisSize: MainAxisSize.min, +// children: [ +// const ListTile( +// leading: Icon(Icons.album, size: 60), +// title: Text( +// 'Sonu Nigam', +// style: TextStyle(fontSize: 30.0) +// ), +// subtitle: Text( +// 'Best of Sonu Nigam Music.', +// style: TextStyle(fontSize: 18.0) +// ), +// ), +// ButtonBar( +// children: [ +// ElevatedButton( +// child: const Text('Play'), +// onPressed: () {/* ... */}, +// ), +// ElevatedButton( +// child: const Text('Pause'), +// onPressed: () {/* ... */}, +// ), +// ], +// ), +// ], +// ), +// ), +// ) + +class PlaceInfo { + final String name; + final String location; + final double rating; + final int paying; + + PlaceInfo(this.name, this.rating, this.location, this.paying); +} diff --git a/ride_share/lib/src/features/home/screens/requests/track_location.dart b/ride_share/lib/src/features/home/screens/requests/track_location.dart new file mode 100644 index 0000000..7ec4acd --- /dev/null +++ b/ride_share/lib/src/features/home/screens/requests/track_location.dart @@ -0,0 +1,148 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_polyline_points/flutter_polyline_points.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:location/location.dart'; +import 'package:ride_share/src/constants/colors.dart'; + +import '../../../../../auth/secrets.dart'; + +class LiveTrackingPage extends StatefulWidget { + const LiveTrackingPage({Key? key}) : super(key: key); + + @override + State createState() => LiveTrackingPageState(); +} + +class LiveTrackingPageState extends State { + final Completer _controller = Completer(); + + static const LatLng sourceLocation = LatLng(37.4221, -122.0841); + static const LatLng destination = LatLng(37.4116, -122.0713); + + List polylineCoordinates = []; + LocationData? currentLocation; + + BitmapDescriptor sourceIcon = BitmapDescriptor.defaultMarker; + BitmapDescriptor destinationIcon = BitmapDescriptor.defaultMarker; + BitmapDescriptor currentLocationIcon = BitmapDescriptor.defaultMarker; + + void getCurrentLocation() async { + Location location = Location(); + + location.getLocation().then( + (location) { + currentLocation = location; + }, + ); + + GoogleMapController googleMapController = await _controller.future; + + location.onLocationChanged.listen((newLoc) { + currentLocation = newLoc; + googleMapController.animateCamera( + CameraUpdate.newCameraPosition( + CameraPosition( + zoom: 13.5, + target: LatLng( + newLoc.latitude!, + newLoc.longitude!, + ), + ), + ), + ); + setState(() {}); + }); + } + + void getPolyPoints() async { + PolylinePoints polylinePoints = PolylinePoints(); + PolylineResult result = await polylinePoints.getRouteBetweenCoordinates( + mapsAPIKey, + PointLatLng(sourceLocation.latitude, sourceLocation.longitude), + PointLatLng(destination.latitude, destination.longitude), + ); + + if (result.points.isNotEmpty) { + result.points.forEach( + (PointLatLng point) => + polylineCoordinates.add(LatLng(point.latitude, point.longitude)), + ); + setState(() {}); + } + } + + void setCustomMarkerIcons() { + BitmapDescriptor.fromAssetImage( + ImageConfiguration.empty, "asset/Pin_source.png") + .then( + (icon) { + sourceIcon = icon; + }, + ); + + BitmapDescriptor.fromAssetImage( + ImageConfiguration.empty, "asset/Pin_destination.png") + .then( + (icon) { + destinationIcon = icon; + }, + ); + + BitmapDescriptor.fromAssetImage(ImageConfiguration.empty, "asset/Badge.png") + .then( + (icon) { + currentLocationIcon = icon; + }, + ); + } + + @override + void initState() { + setCustomMarkerIcons(); + getCurrentLocation(); + getPolyPoints(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text( + "Live Tracking", + style: TextStyle(color: Colors.black, fontSize: 16), + ), + ), + body: GoogleMap( + initialCameraPosition: CameraPosition( + target: LatLng( + currentLocation!.latitude!, currentLocation!.longitude!), + zoom: 13.5), + polylines: { + Polyline( + polylineId: const PolylineId("route"), + points: polylineCoordinates, + color: tSecondaryColor, + width: 6, + ), + }, + markers: { + const Marker( + markerId: MarkerId("source"), position: sourceLocation), + const Marker( + markerId: MarkerId("destination"), position: destination), + Marker( + icon: currentLocationIcon, + markerId: const MarkerId("currentLocation"), + position: LatLng(currentLocation!.latitude!, + currentLocation!.longitude!)), + }, + onMapCreated: (mapController) { + _controller.complete(mapController); + }, + ), + ); + } +} diff --git a/ride_share/lib/src/features/home/screens/ride/offer_ride_screen.dart b/ride_share/lib/src/features/home/screens/ride/offer_ride_screen.dart new file mode 100644 index 0000000..d652780 --- /dev/null +++ b/ride_share/lib/src/features/home/screens/ride/offer_ride_screen.dart @@ -0,0 +1,507 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:intl/intl.dart'; +import 'package:ride_share/src/features/home/controllers/ride_controller.dart'; +import 'package:ride_share/src/models/network_utility.dart'; +import 'package:ride_share/src/models/place_autocomplete_response.dart'; +import 'package:ride_share/src/models/polyline_generator.dart'; +import 'package:ride_share/src/models/user_model.dart'; +import 'package:ride_share/src/repository/user_repository.dart'; +import 'package:weekday_selector/weekday_selector.dart'; +import 'package:geocoding/geocoding.dart' as geoCoding; + +import '../../../../../auth/secrets.dart'; +import '../../../../common_widgets/location_list_tile.dart'; +import '../../../../constants/colors.dart'; +import '../../../../constants/sizes.dart'; +import '../../../../models/autocomplete_prediction.dart'; +import '../../../../models/offered_ride_model.dart'; +import '../../controllers/profile_controller.dart'; + +printIntAsDay(int day) { + print('Received integer: $day. Corresponds to day: ${intDayToEnglish(day)}'); +} + +String intDayToEnglish(int day) { + if (day % 7 == DateTime.monday % 7) return 'Monday'; + if (day % 7 == DateTime.tuesday % 7) return 'Tueday'; + if (day % 7 == DateTime.wednesday % 7) return 'Wednesday'; + if (day % 7 == DateTime.thursday % 7) return 'Thursday'; + if (day % 7 == DateTime.friday % 7) return 'Friday'; + if (day % 7 == DateTime.saturday % 7) return 'Saturday'; + if (day % 7 == DateTime.sunday % 7) return 'Sunday'; + throw '🐞 This should never have happened: $day'; +} + +class OfferRideScreen extends StatefulWidget { + const OfferRideScreen({super.key}); + + @override + State createState() => _OfferRideScreenState(); +} + +class _OfferRideScreenState extends State { + final userController = Get.put(ProfileController()); + // late LatLng destination; + // late LatLng source; + LatLng drop = const LatLng(-1.2955025, 36.6917596); + LatLng source = const LatLng(-1.3261827,36.8396128); + List route = []; + + final values = List.filled(7, false); + TextEditingController mondayTime = TextEditingController(); + TextEditingController tuesdayTime = TextEditingController(); + TextEditingController wednesdayTime = TextEditingController(); + TextEditingController thursdayTime = TextEditingController(); + TextEditingController fridayTime = TextEditingController(); + TextEditingController saturdayTime = TextEditingController(); + + TextEditingController destinationController = TextEditingController(); + TextEditingController sourceController = TextEditingController(); + + @override + void initState() { + mondayTime.text = ""; + tuesdayTime.text = ""; + wednesdayTime.text = ""; + thursdayTime.text = ""; + fridayTime.text = ""; + saturdayTime.text = ""; + super.initState(); + } + + List placePredictions = []; + + void placeAutocomplete(String query) async { + Uri uri = Uri.https( + "maps.googleapis.com", + 'maps/api/place/autocomplete/json', + {"input": query, "key": mapsAPIKey}); + + String? response = await NetworkUtility.fetchUrl(uri); + + if (response != null) { + PlaceAutocompleteResponse result = + PlaceAutocompleteResponse.parseAutocompleteResult(response); + + if (result.predictions != null) { + setState(() { + placePredictions = result.predictions!; + }); + } + } + } + + @override + Widget build(BuildContext context) { + final controller = Get.put(RideController()); + final _formKey = GlobalKey(); + + return Scaffold( + appBar: AppBar( + title: Text("Offer a Ride"), + iconTheme: const IconThemeData( + size: 45.0, weight: 7, fill: 1, color: Colors.black), + ), + body: Form( + key: _formKey, + child: Container( + padding: const EdgeInsets.symmetric(vertical: tFormHeight - 10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextFormField( + cursorColor: tSecondaryColor, + // controller: controller.carMake, + validator: (value) { + if (value == null || value.isEmpty) { + return '*Please enter the car make and model'; + } + return null; + }, + decoration: const InputDecoration( + prefixIcon: Icon(Icons.directions_car), + labelText: "Car Model", + hintText: "Toyota", + border: OutlineInputBorder()), + ), + const SizedBox( + height: tFormHeight, + ), + TextFormField( + cursorColor: tSecondaryColor, + controller: controller.numberPlate, + validator: (value) { + if (value == null || value.isEmpty) { + return '*Please enter the car model'; + } + return null; + }, + decoration: const InputDecoration( + prefixIcon: Icon(Icons.directions_car_filled_outlined), + labelText: "Make of Car", + hintText: "Mark X", + border: OutlineInputBorder()), + ), + const SizedBox( + height: tFormHeight, + ), + // Text( + // 'The days that are currently selected are: ' + // '${valuesToEnglishDays(values, true)}.', + // ), + WeekdaySelector( + // Just some days you want to display to your users. + selectedFillColor: Colors.orange, + displayedDays: const { + DateTime.sunday, + DateTime.monday, + DateTime.tuesday, + DateTime.wednesday, + DateTime.thursday, + DateTime.friday, + }, + onChanged: (v) { + // printIntAsDay(v); + print(values[v % 7]); + setState(() { + values[v % 7] = !values[v % 7]; + }); + }, + values: values, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: SizedBox( + child: TextField( + controller: mondayTime, + readOnly: true, + decoration: InputDecoration( + border: InputBorder.none, + labelText: 'Time', + hintText: '6:00'), + onTap: () async { + TimeOfDay? pickedTime = await showTimePicker( + initialTime: TimeOfDay.now(), + context: context, + ); + if (pickedTime != null) { + print(pickedTime.format(context)); //output 10:51 PM + DateTime parsedTime = DateFormat.jm() + .parse(pickedTime.format(context).toString()); + //converting to DateTime so that we can further format on different pattern. + print(parsedTime); //output 1970-01-01 22:53:00.000 + String formattedTime = + DateFormat('HH:mm:ss').format(parsedTime); + print(formattedTime); //output 14:59:00 + //DateFormat() is from intl package, you can format the time on any pattern you need. + + setState(() { + mondayTime.text = formattedTime; + }); + } else { + print("Time is not selected"); + } + }, + ), + ), + ), + Expanded( + child: SizedBox( + child: TextField( + controller: tuesdayTime, + readOnly: true, + decoration: InputDecoration( + border: InputBorder.none, + labelText: 'Time', + hintText: '6:00'), + onTap: () async { + TimeOfDay? pickedTime = await showTimePicker( + initialTime: TimeOfDay.now(), + context: context, + ); + if (pickedTime != null) { + print(pickedTime.format(context)); //output 10:51 PM + DateTime parsedTime = DateFormat.jm() + .parse(pickedTime.format(context).toString()); + //converting to DateTime so that we can further format on different pattern. + print(parsedTime); //output 1970-01-01 22:53:00.000 + String formattedTime = + DateFormat('HH:mm:ss').format(parsedTime); + print(formattedTime); //output 14:59:00 + //DateFormat() is from intl package, you can format the time on any pattern you need. + + setState(() { + tuesdayTime.text = formattedTime; + }); + } else { + print("Time is not selected"); + } + }, + ), + ), + ), + Expanded( + child: SizedBox( + child: TextField( + controller: wednesdayTime, + readOnly: true, + decoration: InputDecoration( + border: InputBorder.none, + labelText: 'Time', + hintText: '6:00'), + onTap: () async { + TimeOfDay? pickedTime = await showTimePicker( + initialTime: TimeOfDay.now(), + context: context, + ); + if (pickedTime != null) { + print(pickedTime.format(context)); //output 10:51 PM + DateTime parsedTime = DateFormat.jm() + .parse(pickedTime.format(context).toString()); + //converting to DateTime so that we can further format on different pattern. + print(parsedTime); //output 1970-01-01 22:53:00.000 + String formattedTime = + DateFormat('HH:mm:ss').format(parsedTime); + print(formattedTime); //output 14:59:00 + //DateFormat() is from intl package, you can format the time on any pattern you need. + + setState(() { + wednesdayTime.text = formattedTime; + }); + } else { + print("Time is not selected"); + } + }, + ), + ), + ), + Expanded( + child: SizedBox( + child: TextField( + controller: thursdayTime, + readOnly: true, + decoration: InputDecoration( + border: InputBorder.none, + labelText: 'Time', + hintText: '6:00'), + onTap: () async { + TimeOfDay? pickedTime = await showTimePicker( + initialTime: TimeOfDay.now(), + context: context, + ); + if (pickedTime != null) { + print(pickedTime.format(context)); //output 10:51 PM + DateTime parsedTime = DateFormat.jm() + .parse(pickedTime.format(context).toString()); + //converting to DateTime so that we can further format on different pattern. + print(parsedTime); //output 1970-01-01 22:53:00.000 + String formattedTime = + DateFormat('HH:mm:ss').format(parsedTime); + print(formattedTime); //output 14:59:00 + //DateFormat() is from intl package, you can format the time on any pattern you need. + + setState(() { + thursdayTime.text = formattedTime; + }); + } else { + print("Time is not selected"); + } + }, + ), + ), + ), + Expanded( + child: SizedBox( + child: TextField( + controller: fridayTime, + readOnly: true, + decoration: InputDecoration( + border: InputBorder.none, + labelText: 'Time', + hintText: '6:00'), + onTap: () async { + TimeOfDay? pickedTime = await showTimePicker( + initialTime: TimeOfDay.now(), + context: context, + ); + if (pickedTime != null) { + print(pickedTime.format(context)); //output 10:51 PM + DateTime parsedTime = DateFormat.jm() + .parse(pickedTime.format(context).toString()); + //converting to DateTime so that we can further format on different pattern. + print(parsedTime); //output 1970-01-01 22:53:00.000 + String formattedTime = + DateFormat('HH:mm:ss').format(parsedTime); + print(formattedTime); //output 14:59:00 + //DateFormat() is from intl package, you can format the time on any pattern you need. + + setState(() { + fridayTime.text = formattedTime; + }); + } else { + print("Time is not selected"); + } + }, + ), + ), + ), + Expanded( + child: SizedBox( + child: TextField( + controller: saturdayTime, + readOnly: true, + decoration: InputDecoration( + border: InputBorder.none, + labelText: 'Time', + hintText: '6:00'), + onTap: () async { + TimeOfDay? pickedTime = await showTimePicker( + initialTime: TimeOfDay.now(), + context: context, + ); + if (pickedTime != null) { + print(pickedTime.format(context)); //output 10:51 PM + DateTime parsedTime = DateFormat.jm() + .parse(pickedTime.format(context).toString()); + //converting to DateTime so that we can further format on different pattern. + print(parsedTime); //output 1970-01-01 22:53:00.000 + String formattedTime = + DateFormat('HH:mm:ss').format(parsedTime); + print(formattedTime); //output 14:59:00 + //DateFormat() is from intl package, you can format the time on any pattern you need. + + setState(() { + saturdayTime.text = formattedTime; + }); + } else { + print("Time is not selected"); + } + }, + ), + ), + ), + ], + ), + // + TextFormField( + controller: destinationController, + onChanged: (value) { + placeAutocomplete(value); + }, + textInputAction: TextInputAction.search, + decoration: InputDecoration( + hintText: "source location", + ), + ), + const Divider( + height: 4, + thickness: 4, + color: Colors.grey, + ), + Padding( + padding: EdgeInsets.all(10), + child: ElevatedButton( + onPressed: () { + // route = getPolylines(source, drop); + }, + child: Text("get lines")), + ), + const Divider( + height: 4, + thickness: 4, + color: Colors.grey, + ), + Expanded( + child: ListView.builder( + itemCount: placePredictions.length, + itemBuilder: (context, index) => LocationListTile( + press: () async { + String selectedPlace = + placePredictions[index].description!; + destinationController.text = selectedPlace; + + List locations = + await geoCoding.locationFromAddress(selectedPlace); + source = LatLng( + locations.first.latitude, locations.first.longitude); + // route = getPolylines(source, drop); + }, + location: placePredictions[index].description!, + ), + ), + ), + + SizedBox( + width: double.infinity, + child: ElevatedButton( + style: ButtonStyle( + shape: MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(7.0))), + ), + onPressed: () async { + if (_formKey.currentState!.validate()) { + UserModel user = await UserRepository.instance.getUserDetails("+254706446072"); + String polyline = await getPolylines(source, drop); + Map dayAndTime = {}; + List initialTimes = [ + mondayTime.text.trim(), + tuesdayTime.text.trim(), + wednesdayTime.text.trim(), + thursdayTime.text.trim(), + fridayTime.text.trim(), + saturdayTime.text.trim() + ]; + List times = []; + + for(var time in initialTimes){ + if(time.isNotEmpty){ + times.add(time); + } + } + List days = valuesToEnglishDays(values, true); + Map mappings = {}; + + for(int i = 0; i< days.length; i++){ + Map entry= {days[i]: times[i]}; + mappings.addAll(entry); + } + + // final rideDetails = OfferedRideModel( + // userId: user.id!, + // // carModel: controller.carMake.text.trim(), + // numberPlate: controller.numberPlate.text.trim(), + // dayAndTimeAvailable: mappings, + // routePolyline: polyline + // ); + + // RideController.instance.addRide(rideDetails); + + // print(rideDetails); + // print(rideDetails.toJson()); + } + }, + child: Text("ADD RIDE"), + ), + ), + ], + ), + ), + ), + ); + } + + valuesToEnglishDays(List values, bool? searchedValue) { + final days = []; + for (int i = 0; i < values.length; i++) { + final v = values[i]; + // Use v == true, as the value could be null, as well (disabled days). + if (v == searchedValue) days.add(intDayToEnglish(i)); + } + if (days.isEmpty) return 'NONE'; + return days; + } +} diff --git a/ride_share/lib/src/features/home/screens/ride/registration_pages/route_page.dart b/ride_share/lib/src/features/home/screens/ride/registration_pages/route_page.dart new file mode 100644 index 0000000..1d1e926 --- /dev/null +++ b/ride_share/lib/src/features/home/screens/ride/registration_pages/route_page.dart @@ -0,0 +1,178 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:google_maps_webservice/places.dart'; +import 'package:geocoding/geocoding.dart' as geoCoding; +import 'package:ride_share/src/models/polyline_creator.dart'; + +import '../../../../../models/polyline_generator.dart'; +import '../../../controllers/map_controller.dart'; + +class RoutePage extends StatefulWidget { + const RoutePage( + {Key? key, + required this.sourceLocation, + required this.destinationLocation, + required this.encodedPolyline, + required this.onSubmit}) + : super(key: key); + + final String sourceLocation; + final String destinationLocation; + final String encodedPolyline; + + + final Function onSubmit; + + @override + State createState() => _RoutePageState(); +} + +class _RoutePageState extends State { + String btnText = "SUBMIT"; + final sourceController = TextEditingController(); + MapController mapController = Get.put(MapController()); + late LatLng source; + late LatLng destination; + final Set polyline = {}; + String encodedPolyline = ""; + Set markers = Set(); + GoogleMapController? myMapController; + final CameraPosition _kGooglePlex = const CameraPosition( + target: LatLng(-1.3093299, 36.8099464), + zoom: 10, + ); + + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + // select location and show polyline on map for later + TextFormField( + controller: sourceController, + readOnly: true, + onTap: () async { + Prediction? p = await mapController.showGoogleAutoComplete(context); + + String selectedPlace = p!.description!; + + sourceController.text = selectedPlace; + + List locations = + await geoCoding.locationFromAddress(selectedPlace); + + source = + LatLng(locations.first.latitude, locations.first.longitude); + + markers.add(Marker( + markerId: MarkerId(selectedPlace), + infoWindow: InfoWindow( + title: 'Source: $selectedPlace', + ), + position: source, + // icon: BitmapDescriptor.fromBytes(markIcons), + )); + + destination = LatLng(-1.295964, 36.7320972); + + markers.add(Marker( + markerId: const MarkerId("Makini School"), + infoWindow: const InfoWindow( + title: 'Destination: Makini School', + ), + position: destination, + // icon: BitmapDescriptor.fromBytes(markIcons), + )); + + myMapController!.animateCamera(CameraUpdate.newCameraPosition( + CameraPosition(target: source, zoom: 14) + //17 is new zoom level + )); + encodedPolyline = await getPolylines(source, destination); + + + // await getPolylinesDrawn(source, destination); + + + // setState(() { + // showDestinationField = true; + // }); + }, + style: GoogleFonts.poppins( + fontSize: 15, fontWeight: FontWeight.bold, color: Colors.black54), + decoration: InputDecoration( + hintText: 'Pick your source location', + hintStyle: GoogleFonts.poppins( + fontSize: 14, + fontWeight: FontWeight.w400, + color: Colors.black54, + fontStyle: FontStyle.italic), + suffixIcon: const Padding( + padding: EdgeInsets.only(left: 10), + child: Icon( + Icons.search, + color: Colors.black, + ), + ), + border: InputBorder.none, + ), + ), + const SizedBox( + height: 20, + ), + + // The map + Container( + height: Get.height * 0.45, + width: double.infinity, + margin: EdgeInsets.only(left: 30, right: 30), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + border: Border.all( + style: BorderStyle.solid, + ), + ), + child: GoogleMap( + markers: markers, + polylines: polyline, + zoomControlsEnabled: true, + onMapCreated: (GoogleMapController controller) { + myMapController = controller; + }, + initialCameraPosition: _kGooglePlex, + ), + + ), + + const SizedBox( + height: 10, + ), + + // widget.onSubmit(source, destination, polyline); + ElevatedButton( + style: ButtonStyle( + shape: MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(7.0))), + ), + onPressed: () async { + widget.onSubmit(source.toString(), destination.toString(), encodedPolyline); + setState(() { + btnText = "SUBMITTED"; + }); + }, + child: Text(btnText), + ), + ], + ); + } +} diff --git a/ride_share/lib/src/features/home/screens/ride/registration_pages/schedule_page.dart b/ride_share/lib/src/features/home/screens/ride/registration_pages/schedule_page.dart new file mode 100644 index 0000000..c7fa66f --- /dev/null +++ b/ride_share/lib/src/features/home/screens/ride/registration_pages/schedule_page.dart @@ -0,0 +1,313 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:ride_share/src/features/home/screens/ride/ride_registration.dart'; +import 'package:weekday_selector/weekday_selector.dart'; + +class SchedulePage extends StatefulWidget { + SchedulePage({ + Key? key, + required this.onSelect, + required this.availableDaysAndTimes, + }) : super(key: key); + + final Map availableDaysAndTimes; + final Function onSelect; + + @override + State createState() => _SchedulePageState(); +} + +class _SchedulePageState extends State { + final values = List.filled(7, false); + String btnText = "SUBMIT"; + + TextEditingController mondayTime = TextEditingController(); + TextEditingController tuesdayTime = TextEditingController(); + TextEditingController wednesdayTime = TextEditingController(); + TextEditingController thursdayTime = TextEditingController(); + TextEditingController fridayTime = TextEditingController(); + TextEditingController saturdayTime = TextEditingController(); + + @override + void initState() { + mondayTime.text = ""; + tuesdayTime.text = ""; + wednesdayTime.text = ""; + thursdayTime.text = ""; + fridayTime.text = ""; + saturdayTime.text = ""; + super.initState(); + } + + @override + Widget build(BuildContext context) { + final _formKey = GlobalKey(); + DateFormat inputFormat = DateFormat('hh:mm'); + return Scaffold( + body: SingleChildScrollView( + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const Text("Select the days you are available"), + const SizedBox( + height: 10, + ), + WeekdaySelector( + // Just some days you want to display to your users. + selectedFillColor: Colors.orange, + displayedDays: const { + DateTime.sunday, + DateTime.monday, + DateTime.tuesday, + DateTime.wednesday, + DateTime.thursday, + DateTime.friday, + }, + onChanged: (v) { + setState(() { + values[v % 7] = !values[v % 7]; + }); + }, + values: values, + ), + const SizedBox( + height: 20, + ), + const Text("Select the respective times of departure"), + const SizedBox( + height: 15, + ), + Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + TextField( + controller: mondayTime, + readOnly: true, + decoration: InputDecoration( + border: InputBorder.none, + labelText: 'Time on Monday', + hintText: '6:00'), + onTap: () async { + TimeOfDay? pickedTime = await showTimePicker( + initialTime: TimeOfDay.now(), + context: context, + ); + if (pickedTime != null) { + DateTime parsedTime = inputFormat + .parse(pickedTime.format(context).toString()); + String formattedTime = + DateFormat('HH:mm').format(parsedTime); + + setState(() { + mondayTime.text = formattedTime; + }); + } else { + print("Time is not selected"); + } + }, + ), + TextField( + controller: tuesdayTime, + readOnly: true, + decoration: const InputDecoration( + border: InputBorder.none, + labelText: 'Time on Tuesday', + hintText: '6:00'), + onTap: () async { + TimeOfDay? pickedTime = await showTimePicker( + initialTime: TimeOfDay.now(), + context: context, + ); + if (pickedTime != null) { + DateTime parsedTime = inputFormat + .parse(pickedTime.format(context).toString()); + String formattedTime = + DateFormat('HH:mm').format(parsedTime); + + setState(() { + tuesdayTime.text = formattedTime; + }); + } else { + print("Time is not selected"); + } + }, + ), + TextField( + controller: wednesdayTime, + readOnly: true, + decoration: const InputDecoration( + border: InputBorder.none, + labelText: 'Time on Wednesday', + hintText: '6:00'), + onTap: () async { + TimeOfDay? pickedTime = await showTimePicker( + initialTime: TimeOfDay.now(), + context: context, + ); + if (pickedTime != null) { + DateTime parsedTime = inputFormat + .parse(pickedTime.format(context).toString()); + String formattedTime = + DateFormat('HH:mm').format(parsedTime); + + setState(() { + wednesdayTime.text = formattedTime; + }); + } else { + print("Time is not selected"); + } + }, + ), + TextField( + controller: thursdayTime, + readOnly: true, + decoration: const InputDecoration( + border: InputBorder.none, + labelText: 'Time on Thursday', + hintText: '6:00'), + onTap: () async { + TimeOfDay? pickedTime = await showTimePicker( + initialTime: TimeOfDay.now(), + context: context, + ); + if (pickedTime != null) { + DateTime parsedTime = inputFormat + .parse(pickedTime.format(context).toString()); + String formattedTime = + DateFormat('HH:mm').format(parsedTime); + + setState(() { + thursdayTime.text = formattedTime; + }); + } else { + print("Time is not selected"); + } + }, + ), + TextField( + controller: fridayTime, + readOnly: true, + decoration: const InputDecoration( + border: InputBorder.none, + labelText: 'Time on Friday', + hintText: '6:00'), + onTap: () async { + TimeOfDay? pickedTime = await showTimePicker( + initialTime: TimeOfDay.now(), + context: context, + ); + if (pickedTime != null) { + DateTime parsedTime = inputFormat + .parse(pickedTime.format(context).toString()); + String formattedTime = + DateFormat('HH:mm').format(parsedTime); + + setState(() { + fridayTime.text = formattedTime; + }); + } else { + print("Time is not selected"); + } + }, + ), + TextField( + controller: saturdayTime, + readOnly: true, + decoration: const InputDecoration( + border: InputBorder.none, + labelText: 'Time on Saturday', + hintText: '6:00'), + onTap: () async { + TimeOfDay? pickedTime = await showTimePicker( + initialTime: TimeOfDay.now(), + context: context, + ); + if (pickedTime != null) { + DateTime parsedTime = inputFormat + .parse(pickedTime.format(context).toString()); + String formattedTime = + DateFormat('HH:mm').format(parsedTime); + + setState(() { + saturdayTime.text = formattedTime; + }); + } else { + print("Time is not selected"); + } + }, + ), + const SizedBox( + height: 10, + ), + ElevatedButton( + style: ButtonStyle( + shape: MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(7.0))), + ), + onPressed: () async { + Map dayAndTime = {}; + List initialTimes = [ + mondayTime.text.trim(), + tuesdayTime.text.trim(), + wednesdayTime.text.trim(), + thursdayTime.text.trim(), + fridayTime.text.trim(), + saturdayTime.text.trim() + ]; + List times = []; + + for (var time in initialTimes) { + if (time.isNotEmpty) { + times.add(time); + } + } + List days = valuesToEnglishDays(values, true); + Map mappings = {}; + + for (int i = 0; i < days.length; i++) { + Map entry = {days[i]: times[i]}; + mappings.addAll(entry); + } + print(mappings); + widget.onSelect(mappings); + + setState(() { + btnText = "SUBMITTED"; + }); + }, + child: Text(btnText), + ), + ], + ) + ], + ), + ), + ), + ); + } + + String intDayToEnglish(int day) { + if (day % 7 == DateTime.monday % 7) return 'Monday'; + if (day % 7 == DateTime.tuesday % 7) return 'Tueday'; + if (day % 7 == DateTime.wednesday % 7) return 'Wednesday'; + if (day % 7 == DateTime.thursday % 7) return 'Thursday'; + if (day % 7 == DateTime.friday % 7) return 'Friday'; + if (day % 7 == DateTime.saturday % 7) return 'Saturday'; + if (day % 7 == DateTime.sunday % 7) return 'Sunday'; + throw 'This should never have happened: $day'; + } + + valuesToEnglishDays(List values, bool? searchedValue) { + final days = []; + for (int i = 0; i < values.length; i++) { + final v = values[i]; + // Use v == true, as the value could be null, as well (disabled days). + if (v == searchedValue) days.add(intDayToEnglish(i)); + } + if (days.isEmpty) return 'NONE'; + return days; + } +} diff --git a/ride_share/lib/src/features/home/screens/ride/registration_pages/vehicle_details_page.dart b/ride_share/lib/src/features/home/screens/ride/registration_pages/vehicle_details_page.dart new file mode 100644 index 0000000..eebba24 --- /dev/null +++ b/ride_share/lib/src/features/home/screens/ride/registration_pages/vehicle_details_page.dart @@ -0,0 +1,131 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import '../../../../../constants/colors.dart'; +import '../../../../../constants/sizes.dart'; +import '../../../controllers/ride_controller.dart'; + +class VehicleDetailsPage extends StatefulWidget { + const VehicleDetailsPage({ + Key? key, + required this.controller + }) : super(key: key); + + final RideController controller; + + @override + State createState() => _VehicleDetailsPageState(); +} + +class _VehicleDetailsPageState extends State { + final _formKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + return Scaffold( +// resizeToAvoidBottomInset: false, + body: SingleChildScrollView( + + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextFormField( + cursorColor: tSecondaryColor, + controller: widget.controller.vehicleMake, + validator: (value) { + if (value == null || value.isEmpty) { + return '*Please enter the vehicle make'; + } + return null; + }, + decoration: const InputDecoration( + labelText: "Vehicle Make", + hintText: "Toyota", + border: OutlineInputBorder()), + ), + + const SizedBox( + height: tFormHeight-10, + ), + + TextFormField( + cursorColor: tSecondaryColor, + controller: widget.controller.vehicleModel, + validator: (value) { + if (value == null || value.isEmpty) { + return '*Please enter the vehicle model'; + } + return null; + }, + decoration: const InputDecoration( + labelText: "Vehicle Model", + hintText: "Mark X", + border: OutlineInputBorder()), + ), + const SizedBox( + height: tFormHeight-10, + ), + + TextFormField( + cursorColor: tSecondaryColor, + controller: widget.controller.numberPlate, + validator: (value) { + if (value == null || value.isEmpty) { + return '*Please enter the vehicle\'s number plate'; + } + return null; + }, + decoration: const InputDecoration( + labelText: "Number plate", + hintText: "KXX ***X", + border: OutlineInputBorder()), + ), + + const SizedBox( + height: tFormHeight-10, + ), + + TextFormField( + cursorColor: tSecondaryColor, + controller: widget.controller.vehicleColor, + validator: (value) { + if (value == null || value.isEmpty) { + return '*Please enter the vehicle\'s color'; + } + return null; + }, + decoration: const InputDecoration( + labelText: "Color", + hintText: "White", + border: OutlineInputBorder()), + ), + + const SizedBox( + height: tFormHeight-10, + ), + + TextFormField( + cursorColor: tSecondaryColor, + keyboardType: TextInputType.number, + controller: widget.controller.seatsAvailable, + validator: (value) { + if (value == null || value.isEmpty) { + return '*Please enter the number of seats available'; + } + return null; + }, + decoration: const InputDecoration( + labelText: "Seats Available", + hintText: "4", + border: OutlineInputBorder()), + ), + + ], + ), + ), + ), + ); + } +} diff --git a/ride_share/lib/src/features/home/screens/ride/ride_registration.dart b/ride_share/lib/src/features/home/screens/ride/ride_registration.dart new file mode 100644 index 0000000..fa281b6 --- /dev/null +++ b/ride_share/lib/src/features/home/screens/ride/ride_registration.dart @@ -0,0 +1,148 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:ride_share/src/constants/colors.dart'; +import 'package:ride_share/src/features/home/controllers/ride_controller.dart'; +import 'package:ride_share/src/features/home/screens/ride/registration_pages/schedule_page.dart'; +import 'package:ride_share/src/features/home/screens/ride/registration_pages/vehicle_details_page.dart'; +import 'package:ride_share/src/features/home/screens/ride/registration_pages/route_page.dart'; + +import '../../../../common_widgets/registration_header.dart'; +import '../../../../models/offered_ride_model.dart'; +import '../../../../models/user_model.dart'; +import '../../../../repository/user_repository.dart'; + +class RideRegistrationTemplate extends StatefulWidget { + const RideRegistrationTemplate({Key? key}) : super(key: key); + + @override + State createState() => + _RideRegistrationTemplateState(); +} + +class _RideRegistrationTemplateState extends State { + String sourceLocation = ''; + String destinationLocation = ''; + String vehicleMake = ''; + String vehicleModel = ''; + String numberPlate = ''; + String seatsAvailable = ''; + String vehicleColor = ''; + String encodedPolyline = ''; + Map availableDaysAndTimes = {}; + PageController pageController = PageController(); + + RideController rideController = Get.put(RideController()); + int currentPage = 0; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + children: [ + regHeaderPlain( + title: 'Vehicle Registration', + subtitle: 'Fill in your car\'s details'), + const SizedBox(height: 20), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: PageView( + onPageChanged: (int page) { + currentPage = page; + }, + controller: pageController, + physics: NeverScrollableScrollPhysics(), + children: [ + RoutePage( + sourceLocation: sourceLocation, + destinationLocation: destinationLocation, + encodedPolyline: encodedPolyline, + onSubmit: + (String source, String destination, String polyline) { + setState(() { + sourceLocation = source; + destinationLocation = destination; + encodedPolyline = polyline; + }); + }, + ), + VehicleDetailsPage( + controller: rideController, + ), + SchedulePage( + availableDaysAndTimes: availableDaysAndTimes, + onSelect: (Map mappings) { + setState(() { + availableDaysAndTimes = mappings; + }); + }, + ), + ], + ), + ), + ), + Align( + alignment: Alignment.bottomRight, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Obx( + () => isUploading.value + ? Center( + child: CircularProgressIndicator(), + ) + : FloatingActionButton( + onPressed: () { + if (currentPage < 2) { + pageController.animateToPage(currentPage + 1, + duration: const Duration(seconds: 1), + curve: Curves.easeIn); + } else { + uploadVehicleDetails(); + } + }, + child: Icon( + Icons.arrow_forward, + color: Colors.white, + ), + backgroundColor: tSecondaryColor, + ), + ), + )), + ], + ), + ); + } + + var isUploading = false.obs; + + uploadVehicleDetails() async { + isUploading(true); + + UserModel user = + await UserRepository.instance.getUserDetails("+254706446072"); + + // TODO: modify ride-details model to accommodate new fields + + final rideDetails = OfferedRideModel( + userId: user.id!, + userName: user.fullName, + source: sourceLocation, + destination: destinationLocation, + encodedPolyline: encodedPolyline, + vehicleMake: rideController.vehicleMake.text.trim(), + vehicleModel: rideController.vehicleModel.text.trim(), + vehicleColor: rideController.vehicleColor.text.trim(), + seatsAvailable: rideController.seatsAvailable.text.trim(), + numberPlate: rideController.numberPlate.text.trim(), + dayAndTimeAvailable: availableDaysAndTimes, + ); + + + RideController.instance.addRide(rideDetails); + + isUploading(false); + // Get.off(() => SuccessfulUploadScreen()); + } +} diff --git a/ride_share/lib/src/features/home/screens/schedule/user_schedule.dart b/ride_share/lib/src/features/home/screens/schedule/user_schedule.dart new file mode 100644 index 0000000..a6376d8 --- /dev/null +++ b/ride_share/lib/src/features/home/screens/schedule/user_schedule.dart @@ -0,0 +1,149 @@ +import 'dart:ui' as ui; +import 'package:flutter/material.dart'; +import 'package:ride_share/src/constants/colors.dart'; +import 'package:ride_share/src/constants/image_strings.dart'; + +class UserSchedule extends StatefulWidget { + @override + _UserScheduleState createState() => _UserScheduleState(); +} + +class _UserScheduleState extends State { + final double _borderRadius = 24; + + var items = [ + PlaceInfo('Mary Jane', 4.4, 'Chalbi Court', 350), + PlaceInfo('John Doe', 4.0, 'Riverside', 200), + PlaceInfo('Mary Jane', 4.7, 'Junction Mall', 230), + PlaceInfo('Mary Jane', 5.0, 'Lavington Mall', 150), + ]; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Ride Requests'), + backgroundColor: tSecondaryColor, + ), + body: ListView.builder( + itemCount: items.length, + itemBuilder: (context, index) { + return Center( + child: Padding( + padding: const EdgeInsets.all(13.0), + child: Stack( + children: [ + Container( + height: 150, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(_borderRadius), + boxShadow: const [ + BoxShadow( + color: Colors.grey, + blurRadius: 12, + offset: Offset(0, 6), + ), + ], + ), + ), + Positioned.fill( + child: Row( + children: [ + Expanded( + child: Image.asset( + tProfilePic, + height: 64, + width: 64, + ), + flex: 2, + ), + Expanded( + flex: 3, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + items[index].name, + style: TextStyle( + color: Colors.black87, + fontFamily: 'Avenir', + fontWeight: FontWeight.w700), + ), + Text( + items[index].paying.toString(), + style: TextStyle( + color: Colors.black87, + fontFamily: 'Avenir', + ), + ), + SizedBox(height: 16), + Row( + children: [ + Icon( + Icons.location_on, + color: Colors.black87, + size: 16, + ), + SizedBox( + width: 8, + ), + Flexible( + child: Text( + items[index].location, + style: TextStyle( + color: Colors.black87, + fontFamily: 'Avenir', + ), + ), + ), + + ], + ), + ], + ), + ), + + Expanded( + flex: 2, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + items[index].rating.toString(), + style: TextStyle( + color: Colors.black87, + fontFamily: 'Avenir', + fontSize: 18, + fontWeight: FontWeight.w700), + ), + const Icon( + Icons.star, + color: tSecondaryColor, + ), + // RatingBar(rating: items[index].rating), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ); + }, + ), + ); + } +} + +class PlaceInfo { + final String name; + final String location; + final double rating; + final int paying; + + PlaceInfo(this.name, this.rating, this.location, this.paying); +} diff --git a/ride_share/lib/src/models/active_ride_model.dart b/ride_share/lib/src/models/active_ride_model.dart new file mode 100644 index 0000000..c3f5059 --- /dev/null +++ b/ride_share/lib/src/models/active_ride_model.dart @@ -0,0 +1,36 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; + +class ActiveRide { + final String? id; + final String destination; + final bool completed; + final DateTime dateMade; + + ActiveRide({ + this.id, + required this.destination, + required this.completed, + required this.dateMade, + }); + + toJson() { + return { + "Destination": destination, + "Completed": completed, + "Date": dateMade, + }; + } + + factory ActiveRide.fromSnapshot( + DocumentSnapshot> document) { + final data = document.data()!; + + return ActiveRide( + id: document.id, + destination: data["Destination"], + completed: data["Completed"], + dateMade: data["Date"], + ); + } +} diff --git a/ride_share/lib/src/models/autocomplete_prediction.dart b/ride_share/lib/src/models/autocomplete_prediction.dart new file mode 100644 index 0000000..a2d220c --- /dev/null +++ b/ride_share/lib/src/models/autocomplete_prediction.dart @@ -0,0 +1,41 @@ +class AutocompletePrediction { + final String? description; + + final StructuredFormatting? structuredFormatting; + + final String? placeId; + + final String? reference; + + AutocompletePrediction({ + this.description, + this.structuredFormatting, + this.placeId, + this.reference, + }); + + factory AutocompletePrediction.fromJson(Map json) { + return AutocompletePrediction( + description: json['description'] as String?, + placeId: json['place_id'] as String?, + reference: json['reference'] as String?, + structuredFormatting: json['structured_formatting'] != null + ? StructuredFormatting.fromJson(json['structured_formatting']) + : null, + ); + } +} + +class StructuredFormatting { + final String? mainText; + final String? secondaryText; + + StructuredFormatting({this.mainText, this.secondaryText}); + + factory StructuredFormatting.fromJson(Map json) { + return StructuredFormatting( + mainText: json['main_text'] as String?, + secondaryText: json['secondary_text'] as String?, + ); + } +} diff --git a/ride_share/lib/src/models/network_utility.dart b/ride_share/lib/src/models/network_utility.dart new file mode 100644 index 0000000..6f064c8 --- /dev/null +++ b/ride_share/lib/src/models/network_utility.dart @@ -0,0 +1,16 @@ +import 'package:flutter/cupertino.dart'; +import 'package:http/http.dart' as http; + +class NetworkUtility{ + static Future fetchUrl(Uri uri, {Map? headers}) async{ + try{ + final response = await http.get(uri, headers: headers); + if(response.statusCode == 200){ + return response.body; + } + }catch(e){ + debugPrint(e.toString()); + } + return null; + } +} \ No newline at end of file diff --git a/ride_share/lib/src/models/offered_ride_model.dart b/ride_share/lib/src/models/offered_ride_model.dart new file mode 100644 index 0000000..efa1666 --- /dev/null +++ b/ride_share/lib/src/models/offered_ride_model.dart @@ -0,0 +1,68 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; + +class OfferedRideModel { + final String? id; + final String userId; + final String userName; + final String source; + final String destination; + final String encodedPolyline; + final String vehicleMake; + final String vehicleModel; + final String vehicleColor; + final String numberPlate; + final String seatsAvailable; + final Map dayAndTimeAvailable; + + OfferedRideModel( + {this.id, + required this.userId, + required this.userName, + required this.source, + required this.destination, + required this.encodedPolyline, + required this.vehicleMake, + required this.vehicleModel, + required this.vehicleColor, + required this.seatsAvailable, + required this.numberPlate, + required this.dayAndTimeAvailable, + }); + + toJson() { + return { + "UserID": userId, + "UserName": userName, + "Source": source, + "Destination": destination, + "EncodedPolyLine": encodedPolyline, + "VehicleMake": vehicleMake, + "VehicleModel": vehicleModel, + "VehicleColor": vehicleColor, + "SeatsAvailable": seatsAvailable, + "NumberPlate": numberPlate, + "DayAndTimeAvailable": dayAndTimeAvailable, + }; + } + + factory OfferedRideModel.fromSnapshot( + DocumentSnapshot> document) { + final data = document.data()!; + + return OfferedRideModel( + id: document.id, + userId: data["UserID"], + userName: data["UserName"], + source: data["Source"], + destination: data["Destination"], + vehicleModel: data["VehicleModel"], + vehicleMake: data["VehicleMake"], + vehicleColor: data["VehicleColor"], + numberPlate: data["NumberPlate"], + seatsAvailable: data["SeatsAvailable"], + dayAndTimeAvailable: data["DayAndTimeAvailable"], + encodedPolyline: data["EncodedPolyLine"] + ); + } +} diff --git a/ride_share/lib/src/models/place_autocomplete_response.dart b/ride_share/lib/src/models/place_autocomplete_response.dart new file mode 100644 index 0000000..ae6b77a --- /dev/null +++ b/ride_share/lib/src/models/place_autocomplete_response.dart @@ -0,0 +1,28 @@ +import 'dart:convert'; + +import 'autocomplete_prediction.dart'; + +class PlaceAutocompleteResponse { + final String? status; + final List? predictions; + + PlaceAutocompleteResponse({this.status, this.predictions}); + + factory PlaceAutocompleteResponse.fromJson(Map json) { + return PlaceAutocompleteResponse( + status: json['status'] as String?, + predictions: json['predictions'] != null + ? json['predictions'] + .map( + (json) => AutocompletePrediction.fromJson(json)) + .toList() + : null, + ); + } + + static PlaceAutocompleteResponse parseAutocompleteResult(String responseBody){ + final parsed = json.decode(responseBody).cast(); + + return PlaceAutocompleteResponse.fromJson(parsed); + } +} diff --git a/ride_share/lib/src/models/polyline_creator.dart b/ride_share/lib/src/models/polyline_creator.dart new file mode 100644 index 0000000..1e280f8 --- /dev/null +++ b/ride_share/lib/src/models/polyline_creator.dart @@ -0,0 +1,113 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:http/http.dart' as http; +import 'package:ride_share/src/constants/colors.dart'; + +import '../../auth/secrets.dart'; + +List polyList = []; +bool internet = true; + +getPolylinesDrawn(LatLng pickUp, LatLng drop) async { + polyList.clear(); + String pickLat = ''; + String pickLng = ''; + String dropLat = ''; + String dropLng = ''; + + pickLat = pickUp.latitude.toString(); + pickLng = pickUp.longitude.toString(); + dropLat = drop.latitude.toString(); + dropLng = drop.longitude.toString(); + + try { + var response = await http.get(Uri.parse( + 'https://maps.googleapis.com/maps/api/directions/json?origin=$pickLat%2C$pickLng&destination=$dropLat%2C$dropLng&avoid=ferries|indoor&transit_mode=bus&mode=driving&key=$mapsAPIKey')); + if (response.statusCode == 200) { + var steps = + jsonDecode(response.body)['routes'][0]['overview_polyline']['points']; + decodeEncodedPolyline(steps); + } else { + debugPrint(response.body); + } + } catch (e) { + if (e is SocketException) { + internet = false; + } + } + return polyList; +} + +//polyline decode + +Set polyline = {}; + +List decodeEncodedPolyline(String encoded) { + List poly = []; + int index = 0, len = encoded.length; + int lat = 0, lng = 0; + polyline.clear(); + + while (index < len) { + int b, shift = 0, result = 0; + do { + b = encoded.codeUnitAt(index++) - 63; + result |= (b & 0x1f) << shift; + shift += 5; + } while (b >= 0x20); + int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1)); + lat += dlat; + + shift = 0; + result = 0; + do { + b = encoded.codeUnitAt(index++) - 63; + result |= (b & 0x1f) << shift; + shift += 5; + } while (b >= 0x20); + int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1)); + lng += dlng; + LatLng p = LatLng((lat / 1E5).toDouble(), (lng / 1E5).toDouble()); + polyList.add(p); + } + + polyline.add( + Polyline( + polylineId: const PolylineId('1'), + color: tSecondaryColor, + visible: true, + width: 4, + points: polyList), + ); + + return poly; +} + +class PointLatLng { + /// Creates a geographical location specified in degrees [latitude] and + /// [longitude]. + /// + const PointLatLng(double latitude, double longitude) + // ignore: unnecessary_null_comparison + : assert(latitude != null), + // ignore: unnecessary_null_comparison + assert(longitude != null), + // ignore: unnecessary_this, prefer_initializing_formals + this.latitude = latitude, + // ignore: unnecessary_this, prefer_initializing_formals + this.longitude = longitude; + + /// The latitude in degrees. + final double latitude; + + /// The longitude in degrees + final double longitude; + + @override + String toString() { + return "lat: $latitude / longitude: $longitude"; + } +} diff --git a/ride_share/lib/src/models/polyline_generator.dart b/ride_share/lib/src/models/polyline_generator.dart new file mode 100644 index 0000000..d0a9d86 --- /dev/null +++ b/ride_share/lib/src/models/polyline_generator.dart @@ -0,0 +1,142 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:http/http.dart' as http; + +import '../../auth/secrets.dart'; + +List polyList = []; +bool internet = true; + +// Future> getPolylines(LatLng pickUp, LatLng drop) async { +// polyList.clear(); +// String pickLat = ''; +// String pickLng = ''; +// String dropLat = ''; +// String dropLng = ''; +// +// pickLat = pickUp.latitude.toString(); +// pickLng = pickUp.longitude.toString(); +// dropLat = drop.latitude.toString(); +// dropLng = drop.longitude.toString(); +// +// try { +// var response = await http.get(Uri.parse( +// 'https://maps.googleapis.com/maps/api/directions/json?origin=$pickLat%2C$pickLng&destination=$dropLat%2C$dropLng&avoid=ferries|indoor&transit_mode=bus&mode=driving&key=')); +// if (response.statusCode == 200) { +// var steps = +// jsonDecode(response.body)['routes'][0]['overview_polyline']['points']; +// decodeEncodedPolyline(steps); +// } else { +// debugPrint(response.body); +// } +// } catch (e) { +// if (e is SocketException) { +// internet = false; +// } +// } +// return polyList; +// } +var steps = ""; + +Future getPolylines(LatLng pickUp, LatLng drop) async { + polyList.clear(); + String pickLat = ''; + String pickLng = ''; + String dropLat = ''; + String dropLng = ''; + + pickLat = pickUp.latitude.toString(); + pickLng = pickUp.longitude.toString(); + dropLat = drop.latitude.toString(); + dropLng = drop.longitude.toString(); + + try { + var response = await http.get(Uri.parse( + 'https://maps.googleapis.com/maps/api/directions/json?origin=$pickLat%2C$pickLng&destination=$dropLat%2C$dropLng&avoid=ferries|indoor&transit_mode=bus&mode=driving&key=$mapsAPIKey')); + if (response.statusCode == 200) { + steps = jsonDecode(response.body)['routes'][0]['overview_polyline']['points']; + // decodeEncodedPolyline(steps); + } else { + debugPrint(response.body); + } + } catch (e) { + if (e is SocketException) { + internet = false; + } + } + return steps; +} + +Set polyline = {}; + +List decodeEncodedPolyline(String encoded) { + List poly = []; + int index = 0, len = encoded.length; + int lat = 0, lng = 0; + polyline.clear(); + + while (index < len) { + int b, shift = 0, result = 0; + do { + b = encoded.codeUnitAt(index++) - 63; + result |= (b & 0x1f) << shift; + shift += 5; + } while (b >= 0x20); + int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1)); + lat += dlat; + + shift = 0; + result = 0; + do { + b = encoded.codeUnitAt(index++) - 63; + result |= (b & 0x1f) << shift; + shift += 5; + } while (b >= 0x20); + int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1)); + lng += dlng; + LatLng p = LatLng((lat / 1E5).toDouble(), (lng / 1E5).toDouble()); + String s = p.toString(); + polyList.add(s); + } + // + // polyline.add( + // Polyline( + // polylineId: const PolylineId('1'), + // color: Colors.orange, + // visible: true, + // width: 4, + // points: polyList), + // ); + + return poly; +} + +class PointLatLng { + /// Creates a geographical location specified in degrees [latitude] and + /// [longitude]. + /// + const PointLatLng(double latitude, double longitude) + // ignore: unnecessary_null_comparison + : assert(latitude != null), + // ignore: unnecessary_null_comparison + assert(longitude != null), + // ignore: unnecessary_this, prefer_initializing_formals + this.latitude = latitude, + // ignore: unnecessary_this, prefer_initializing_formals + this.longitude = longitude; + + /// The latitude in degrees. + final double latitude; + + /// The longitude in degrees + final double longitude; + + @override + String toString() { + return "lat: $latitude / longitude: $longitude"; + } +} diff --git a/ride_share/lib/src/models/ride_request_model.dart b/ride_share/lib/src/models/ride_request_model.dart new file mode 100644 index 0000000..34e9b23 --- /dev/null +++ b/ride_share/lib/src/models/ride_request_model.dart @@ -0,0 +1,52 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; + +class RideRequestModel { + final String? id; + final String rideId; + final String ownerId; + final String requestedByUser; + final String pickupLocation; + final String seatsAvailable; + final double similarityScore; + final bool acceptedStatus; + + RideRequestModel( + {this.id, + required this.rideId, + required this.ownerId, + required this.requestedByUser, + required this.pickupLocation, + required this.seatsAvailable, + required this.similarityScore, + required this.acceptedStatus, + }); + + toJson() { + return { + "RideId": rideId, + "OwnerId": ownerId, + "RequestedByUser": requestedByUser, + "PickupLocation": pickupLocation, + "SeatsAvailable": seatsAvailable, + "SimilarityScore": similarityScore, + "AcceptedStatus": acceptedStatus, + }; + } + + factory RideRequestModel.fromSnapshot( + DocumentSnapshot> document) { + final data = document.data()!; + + return RideRequestModel( + id: document.id, + rideId: data["RideId"], + ownerId: data["OwnerId"], + requestedByUser: data["RequestedByUser"], + pickupLocation: data["PickupLocation"], + seatsAvailable: data["SeatsAvailable"], + similarityScore: data["SimilarityScore"], + acceptedStatus: data["AcceptedStatus"], + ); + } +} diff --git a/ride_share/lib/src/models/user_model.dart b/ride_share/lib/src/models/user_model.dart new file mode 100644 index 0000000..e6ad253 --- /dev/null +++ b/ride_share/lib/src/models/user_model.dart @@ -0,0 +1,69 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; + +class UserModel { + final String? id; + final String fullName; + final String email; + final String phoneNo; + late final String password; + bool? emailVerified; + + UserModel({ + this.id, + required this.email, + required this.password, + required this.fullName, + required this.phoneNo, + this.emailVerified + }); + + toJsonInitial() { + return { + "FullName": fullName, + "Email": email, + "Password": password, + "EmailVerified": false, + "Phone": phoneNo, + }; + } + + toJson() { + return { + "FullName": fullName, + "Email": email, + "Password": password, + "EmailVerified": emailVerified, + "Phone": phoneNo, + }; + } + + factory UserModel.fromSnapshot( + DocumentSnapshot> document) { + final data = document.data()!; + + return UserModel( + id: document.id, + email: data["Email"], + password: data["Password"], + fullName: data["FullName"], + emailVerified: data["EmailVerified"], + phoneNo: data["Phone"], + ); + + + } + + factory UserModel.fromSnapshotVerify( + DocumentSnapshot> document) { + final data = document.data()!; + + return UserModel( + id: document.id, + email: data["Email"], + password: data["Password"], + fullName: data["FullName"], + emailVerified: true, + phoneNo: data["Phone"], + ); + } +} \ No newline at end of file diff --git a/ride_share/lib/src/repository/authentication_repository.dart b/ride_share/lib/src/repository/authentication_repository.dart new file mode 100644 index 0000000..bb843cb --- /dev/null +++ b/ride_share/lib/src/repository/authentication_repository.dart @@ -0,0 +1,203 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:dbcrypt/dbcrypt.dart'; +import 'package:email_auth/email_auth.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/foundation.dart'; +import 'package:get/get.dart'; +import 'package:ride_share/auth.config.dart'; +import 'package:ride_share/src/features/home/screens/home_screen.dart'; +import 'package:ride_share/src/repository/signup_failure.dart'; +import 'package:ride_share/src/repository/user_repository.dart'; + +import '../features/authentication/screens/welcome/welcome_screen.dart'; +import '../models/user_model.dart'; + +class AuthenticationRepository extends GetxController { + static AuthenticationRepository get instance => Get.find(); + + final _auth = FirebaseAuth.instance; + late final Rx firebaseUser; + var verificationId = ''.obs; + final _userRepo = Get.put(UserRepository()); + final _db = FirebaseFirestore.instance; + EmailAuth emailAuth = EmailAuth(sessionName: "Email OTP session"); + + @override + void onReady() { + firebaseUser = Rx(_auth.currentUser); + firebaseUser.bindStream(_auth.userChanges()); + ever(firebaseUser, _setInitialScreen); + emailAuth.config(remoteServerConfiguration); + } + + _setInitialScreen(User? user) { + user == null + ? Get.offAll(() => const WelcomeScreen()) + : Get.offAll(() => const HomeScreen()); + } + + Future phoneAuthentication(String phoneNo) async { + await _auth.verifyPhoneNumber( + phoneNumber: phoneNo, + verificationCompleted: (credential) async { + await _auth.signInWithCredential(credential); + // await _auth.currentUser?.linkWithCredential(credential); + }, + codeSent: (verificationId, resendToken) { + this.verificationId.value = verificationId; + }, + codeAutoRetrievalTimeout: (verificationId) { + this.verificationId.value = verificationId; + }, + verificationFailed: (e) { + if (e.code == 'invalid-phone-no') { + Get.snackbar('Error', 'The phone number provided is invalid'); + } else { + Get.snackbar('Error', 'Something went wrong ${e.code}'); + } + }, + ); + } + + Future verifyOTP(String otp) async { + var credentials = await _auth.signInWithCredential( + PhoneAuthProvider.credential( + verificationId: verificationId.value, smsCode: otp)); + return credentials.user != null ? true : false; + } + + // Future verifyForgotOTP(String otp) async { + // var credentials = await _auth.signInWithCredential( + // PhoneAuthProvider.credential( + // verificationId: verificationId.value, smsCode: otp)); + // return credentials.user != null ? true : false; + // } + + Future loginUserWithPhoneAndPassword(String phone, String password) async{ + print("$phone $password"); + UserModel userData = await getUserDetails(phone); + // var matches = isCorrect(password, userData.password); + var matches = (password == userData.password); + if(matches){ + return true; + } + return false; + } + + void sendEmailOtp(String userEmail) async{ + bool res = await emailAuth.sendOtp(recipientMail: userEmail, otpLength: 6); + if(res){ + print("OTP sent"); + }else{ + print("Error! OTP not sent"); + } + } + + Future verifyEmailOTP(String userEmail, String userOTP) async{ + var res = emailAuth.validateOtp(recipientMail: userEmail, userOtp: userOTP); + if(res){ + return true; + }else{ + return false; + } + } + + bool isCorrect(String pwd, String hashed){ + return DBCrypt().checkpw(pwd, hashed); + } + + Future getUserDetails(String? phone) async { + final snapshot = await _db.collection("Users").where("Phone", isEqualTo: phone).get(); + final userData = snapshot.docs.map((e) => UserModel.fromSnapshot(e)).single; + return userData; + } + + + Future logout() async { + await _auth.signOut(); + Get.offAll(() => const WelcomeScreen()); + } + + // Future createUserWithEmailAndPassword(String email, + // String password) async { + // try { + // await _auth.createUserWithEmailAndPassword( + // email: email, password: password); + // return firebaseUser.value != null + // ? true + // : false; + // } on FirebaseAuthException catch (e) { + // final ex = SignUpMailPasswordFailure.code(e.code); + // if (kDebugMode) { + // print('FIREBASE AUTH EXCEPTION - ${ex.message}'); + // } + // throw ex; + // } catch (_) { + // const ex = SignUpMailPasswordFailure(); + // if (kDebugMode) { + // print('EXCEPTION - ${ex.message}'); + // } + // throw ex; + // } + // } + + // Future loginUserWithEmailAndPassword(String email, + // String password) async { + // try { + // await _auth.signInWithEmailAndPassword(email: email, password: password); + // return firebaseUser.value != null ? true : false; + // } on FirebaseAuthException catch (e) { + // e.code; + // } catch (_) {} + // return false; + // } + + // + // void signInWithFacebook() async{ + // + // try{ + // final fbLoginResult = await FacebookAuth.instance.login(); + // final userData = await FacebookAuth.instance.getUserData(); + // + // final fbCredential = FacebookAuthProvider.credential(fbLoginResult.accessToken!.token); + // await FirebaseAuth.instance.signInWithCredential(fbCredential); + // + // await FirebaseFirestore.instance.collection("Users").add({ + // 'Email': userData['email'], + // 'FullName': userData['name'], + // }); + // } on FirebaseAuthException catch (e){ + // var content = ''; + // switch(e.code){ + // case 'account-exists-with-different-credential': + // content = "This account exists with a different sign in provider"; + // break; + // + // case 'invalid-credential': + // content = "An unknown error has occurred"; + // break; + // case 'operation-not-allowed': + // content = "This operation is not allowed"; + // break; + // case 'user-disabled': + // content = "The account you tried to log in as is disabled"; + // break; + // case 'user-not-found': + // content = "The account you tried to log in as does not exist"; + // break; + // } + // + // showDialog(context: context as BuildContext, builder: (context) => AlertDialog( + // title: const Text('log in with facebook failed'), + // content: Text(content), + // actions: [TextButton(onPressed: () { + // Navigator.of(context).pop(); + // }, child: const Text('Ok'))], + // )); + // + // } finally { + // + // } + // } + +} \ No newline at end of file diff --git a/ride_share/lib/src/repository/ride_repository.dart b/ride_share/lib/src/repository/ride_repository.dart new file mode 100644 index 0000000..853b417 --- /dev/null +++ b/ride_share/lib/src/repository/ride_repository.dart @@ -0,0 +1,111 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:ride_share/src/models/active_ride_model.dart'; +import 'package:ride_share/src/models/offered_ride_model.dart'; +import 'package:ride_share/src/models/ride_request_model.dart'; + +class RideRepository extends GetxController { + static RideRepository get instance => Get.find(); + + final _db = FirebaseFirestore.instance; + + //add ride + Future addRide(OfferedRideModel rideDetails) async { + await _db + .collection("OfferedRides") + .add(rideDetails.toJson()) + .whenComplete( + () => Get.snackbar("Success", "Your ride has been added", + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.green.withOpacity(0.1), + colorText: Colors.green), + ) + .catchError((error, stackTrace) { + Get.snackbar("Error", "Something went wrong. Try again", + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red.withOpacity(0.1), + colorText: Colors.red); + if (kDebugMode) { + print("Error - $error"); + } + }); + } + + //fetch all rides + Future> allRides() async { + + final snapshot = await _db.collection("OfferedRides").get(); + final rides = snapshot.docs.map((e) => OfferedRideModel.fromSnapshot(e)).toList(); + return rides; + + } + + + //make ride request + Future makeRideRequest(RideRequestModel rideRequest) async { + await _db + .collection("RideRequests") + .add(rideRequest.toJson()) + .whenComplete( + () => Get.snackbar("Success", "Your request has been made", + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.green.withOpacity(0.1), + colorText: Colors.green), + ) + .catchError((error, stackTrace) { + Get.snackbar("Error", "Something went wrong. Try again", + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red.withOpacity(0.1), + colorText: Colors.red); + if (kDebugMode) { + print("Error - $error"); + } + }); + } + + //fetch all ride requests + Future> allRideRequests(String userId) async { + final snapshot = await _db.collection("RideRequests").where("OwnerId", isEqualTo: userId).get(); + final requests= snapshot.docs.map((e) => RideRequestModel.fromSnapshot(e)).toList(); + return requests; + } + + //accept ride request + Future acceptRideRequest(RideRequestModel rideRequest) async { + await _db.collection("RideRequests").doc(rideRequest.id).update(rideRequest.toJson()); + } + + //decline ride request + Future declineRideRequest(RideRequestModel rideRequest) async { + await _db.collection("RideRequests").doc(rideRequest.id).delete(); + } + + //add active ride + Future addActiveRide(ActiveRide activeRide) async { + await _db + .collection("ActiveRides") + .add(activeRide.toJson()) + .whenComplete( + () => Get.snackbar("Success", "Your ride has started", + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.green.withOpacity(0.1), + colorText: Colors.green), + ) + .catchError((error, stackTrace) { + Get.snackbar("Error", "Something went wrong. Try again", + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red.withOpacity(0.1), + colorText: Colors.red); + if (kDebugMode) { + print("Error - $error"); + } + }); + } + + //remove active ride + Future removeActiveRide(ActiveRide activeRide) async { + await _db.collection("ActiveRides").doc(activeRide.id).update(activeRide.toJson()); + } +} diff --git a/ride_share/lib/src/repository/signup_failure.dart b/ride_share/lib/src/repository/signup_failure.dart new file mode 100644 index 0000000..b6bf80e --- /dev/null +++ b/ride_share/lib/src/repository/signup_failure.dart @@ -0,0 +1,32 @@ +class SignUpMailPasswordFailure { + final String message; + + const SignUpMailPasswordFailure([this.message = "An Unkown error occurred"]); + + factory SignUpMailPasswordFailure.code(String code) { + switch (code) { + case 'weak-password': + return const SignUpMailPasswordFailure( + 'Please enter a stronger password.'); + + case 'invalid-email': + return const SignUpMailPasswordFailure( + 'Email is invalid or badly formatted.'); + + case 'email-already-in-use': + return const SignUpMailPasswordFailure( + 'An account already exists for that email.'); + + case 'operation-not-allowed': + return const SignUpMailPasswordFailure( + 'This operation is not allowed.'); + + case 'user-disabled': + return const SignUpMailPasswordFailure( + 'This user has been disabled. Please contact support for help.'); + + default: + return const SignUpMailPasswordFailure(); + } + } +} \ No newline at end of file diff --git a/ride_share/lib/src/repository/user_repository.dart b/ride_share/lib/src/repository/user_repository.dart new file mode 100644 index 0000000..857c499 --- /dev/null +++ b/ride_share/lib/src/repository/user_repository.dart @@ -0,0 +1,54 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_database/firebase_database.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +import '../models/user_model.dart'; + +class UserRepository extends GetxController { + static UserRepository get instance => Get.find(); + + final _db = FirebaseFirestore.instance; + // late DatabaseReference dbRef = FirebaseDatabase.instance.ref().child("users"); + + Future createUser(UserModel user) async { + await _db + .collection("Users") + .add(user.toJsonInitial()) + .whenComplete( + () => Get.snackbar("Success", "Your account has been created", + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.green.withOpacity(0.1), + colorText: Colors.green), + ) + .catchError((error, stackTrace) { + Get.snackbar("Error", "Something went wrong. Try again", + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red.withOpacity(0.1), + colorText: Colors.red); + if (kDebugMode) { + print("Error - $error"); + } + }); + } + // + // Future createRealTimeUser(String fullname, String email, String phone) async{ + // Map users = { + // 'Fullname': fullname, + // 'Email': email, + // 'PhoneNo': phone, + // }; + // await dbRef.push().set(users); + // } + + Future getUserDetails(String? phone) async { + final snapshot = await _db.collection("Users").where("Phone", isEqualTo: phone).get(); + final userData = snapshot.docs.map((e) => UserModel.fromSnapshot(e)).single; + return userData; + } + + Future updateUser(UserModel user) async { + await _db.collection("Users").doc(user.id).update(user.toJson()); + } +} \ No newline at end of file diff --git a/ride_share/macos/Flutter/GeneratedPluginRegistrant.swift b/ride_share/macos/Flutter/GeneratedPluginRegistrant.swift index e777c67..90b6f61 100644 --- a/ride_share/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/ride_share/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,8 +5,20 @@ import FlutterMacOS import Foundation +import cloud_firestore +import firebase_auth +import firebase_core +import firebase_database +import location +import package_info_plus import path_provider_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin")) + FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) + FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) + FLTFirebaseDatabasePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseDatabasePlugin")) + LocationPlugin.register(with: registry.registrar(forPlugin: "LocationPlugin")) + FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/ride_share/pubspec.lock b/ride_share/pubspec.lock index a344ad1..780cebd 100644 --- a/ride_share/pubspec.lock +++ b/ride_share/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: a742f71d7f3484253a623b30e19256aa4668ecbb3de6ad1beb0bcf8d4777ecd8 + url: "https://pub.dev" + source: hosted + version: "1.3.3" adobe_xd: dependency: "direct main" description: @@ -57,6 +65,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + cloud_firestore: + dependency: "direct main" + description: + name: cloud_firestore + sha256: "5bbc1f5bffa79af54ca035b92b57f81c6fb35ee5471ead67e29c8e12de8432f8" + url: "https://pub.dev" + source: hosted + version: "4.8.2" + cloud_firestore_platform_interface: + dependency: transitive + description: + name: cloud_firestore_platform_interface + sha256: "8e0aafeb727087f84710275d59a101b2acf2290ffbb3b111aab70423f8350d5d" + url: "https://pub.dev" + source: hosted + version: "5.15.2" + cloud_firestore_web: + dependency: transitive + description: + name: cloud_firestore_web + sha256: bbf0ebb9d1e9251caa00e8727389313c64cb4240c1c31f895971c52d0c782316 + url: "https://pub.dev" + source: hosted + version: "3.6.2" collection: dependency: transitive description: @@ -97,6 +129,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + dbcrypt: + dependency: "direct main" + description: + name: dbcrypt + sha256: b32a786486509ddc31b1fa97932a2fc9a3838bbed5d82e43bd636604c96b031e + url: "https://pub.dev" + source: hosted + version: "2.0.0" + email_auth: + dependency: "direct main" + description: + name: email_auth + sha256: a382b6d510c2f6a5dad5e424b693168c65681fff29d36d0abec7916472aaba1b + url: "https://pub.dev" + source: hosted + version: "1.0.0" email_validator: dependency: "direct main" description: @@ -129,19 +177,112 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.4" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + sha256: f693c0aa998b1101453878951b171b69f0db5199003df1c943b33493a1de7917 + url: "https://pub.dev" + source: hosted + version: "4.6.3" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + sha256: "689ae048b78ad088ba31acdec45f5badb56201e749ed8b534947a7303ddb32aa" + url: "https://pub.dev" + source: hosted + version: "6.15.3" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + sha256: f35d637a1707afd51f30090bb5234b381d5071ccbfef09b8c393bc7c65e440cd + url: "https://pub.dev" + source: hosted + version: "5.5.3" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: a4a99204da264a0aa9d54a332ea0315ce7b0768075139c77abefe98093dd98be + url: "https://pub.dev" + source: hosted + version: "2.14.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: b63e3be6c96ef5c33bdec1aab23c91eb00696f6452f0519401d640938c94cba2 + url: "https://pub.dev" + source: hosted + version: "4.8.0" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: "0fd5c4b228de29b55fac38aed0d9e42514b3d3bd47675de52bf7f8fccaf922fa" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + firebase_database: + dependency: "direct main" + description: + name: firebase_database + sha256: "36699bda00feb31433606f034078c690f148c15a4721b351a83c796ba4bba4e7" + url: "https://pub.dev" + source: hosted + version: "10.2.3" + firebase_database_platform_interface: + dependency: transitive + description: + name: firebase_database_platform_interface + sha256: "8bd62f80b51d71a81087a33fe2e2a868fb818a6e33b319a137a7bcb35dec262d" + url: "https://pub.dev" + source: hosted + version: "0.2.5+3" + firebase_database_web: + dependency: transitive + description: + name: firebase_database_web + sha256: "05e6b0c2a192569cd5ce2be4a708756ca9f56e4c7b6fe1916f0f9d96a826f235" + url: "https://pub.dev" + source: hosted + version: "0.2.3+3" + fl_country_code_picker: + dependency: "direct main" + description: + name: fl_country_code_picker + sha256: fa513b167c5333e7829a9707c84898f01a328a31c2a799b3c7d3de508fb9760f + url: "https://pub.dev" + source: hosted + version: "0.1.5" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_google_places: + dependency: "direct main" + description: + name: flutter_google_places + sha256: e9fb23ceacdc7359aa759627550146f7fd3ae6c067d16f39f3c50d8feebf4809 + url: "https://pub.dev" + source: hosted + version: "0.3.0" flutter_lints: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" + flutter_localizations: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_native_splash: dependency: "direct main" description: @@ -158,6 +299,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360" + url: "https://pub.dev" + source: hosted + version: "2.0.15" + flutter_polyline_points: + dependency: "direct main" + description: + name: flutter_polyline_points + sha256: "02699e69142f51a248d784b6e3eec524194467fca5f7c4da19699ce2368b6980" + url: "https://pub.dev" + source: hosted + version: "1.0.0" flutter_svg: dependency: "direct main" description: @@ -176,6 +333,38 @@ packages: description: flutter source: sdk version: "0.0.0" + geocoding: + dependency: "direct main" + description: + name: geocoding + sha256: b34c0501bbbaf3190b85bef3078b27cf66c28a8915c6d3af50d67f356aa7da31 + url: "https://pub.dev" + source: hosted + version: "2.1.0" + geocoding_android: + dependency: transitive + description: + name: geocoding_android + sha256: "5a1fc0cec9b0497b44ca31c1fa8d1c891f3aded1053e6bb2eac075d3bd1bf046" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + geocoding_ios: + dependency: transitive + description: + name: geocoding_ios + sha256: c85495ce8fb34e4fbd2dd8fc5f79263d622d9f88c4af948c965daf6b27a7f3a1 + url: "https://pub.dev" + source: hosted + version: "2.1.0" + geocoding_platform_interface: + dependency: transitive + description: + name: geocoding_platform_interface + sha256: "8848605d307d844d89937cdb4b8ad7dfa880552078f310fa24d8a460f6dddab4" + url: "https://pub.dev" + source: hosted + version: "2.0.1" get: dependency: "direct main" description: @@ -184,6 +373,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.6.5" + google_api_headers: + dependency: transitive + description: + name: google_api_headers + sha256: b27a55935d5c51cedda8a925f5df8388cc327c94a47fef5a4335e8707e089878 + url: "https://pub.dev" + source: hosted + version: "1.6.0" google_fonts: dependency: "direct main" description: @@ -192,6 +389,62 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.4" + google_maps: + dependency: transitive + description: + name: google_maps + sha256: "555d5d736339b0478e821167ac521c810d7b51c3b2734e6802a9f046b64ea37a" + url: "https://pub.dev" + source: hosted + version: "6.3.0" + google_maps_flutter: + dependency: "direct main" + description: + name: google_maps_flutter + sha256: "7b417a64ee7a060f42cf44d8c274d3b562423f6fe57d2911b7b536857c0d8eb6" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + google_maps_flutter_android: + dependency: transitive + description: + name: google_maps_flutter_android + sha256: "9512c862df77c1f0fa5f445513dd3c57f5996f0a809dccb74e54b690ee4e3a0f" + url: "https://pub.dev" + source: hosted + version: "2.4.15" + google_maps_flutter_ios: + dependency: transitive + description: + name: google_maps_flutter_ios + sha256: a9462a433bf3ebe60aadcf4906d2d6341a270d69d3e0fcaa8eb2b64699fcfb4f + url: "https://pub.dev" + source: hosted + version: "2.2.3" + google_maps_flutter_platform_interface: + dependency: transitive + description: + name: google_maps_flutter_platform_interface + sha256: "308f0af138fa78e8224d598d46ca182673874d0ef4d754b7157c073b5b4b8e0d" + url: "https://pub.dev" + source: hosted + version: "2.2.7" + google_maps_flutter_web: + dependency: transitive + description: + name: google_maps_flutter_web + sha256: "280170a2dcac3364317b5786f0d2e3c4128fdb795bc0d87ffe56226b0cf1f57d" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + google_maps_webservice: + dependency: "direct main" + description: + name: google_maps_webservice + sha256: d0ae4e4508afd74a3f051565261a3cdbae59db29448f9b6e6beb5674545e1eb7 + url: "https://pub.dev" + source: hosted + version: "0.0.20-nullsafety.5" html: dependency: transitive description: @@ -224,6 +477,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.17" + intl: + dependency: "direct main" + description: + name: intl + sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91" + url: "https://pub.dev" + source: hosted + version: "0.17.0" js: dependency: transitive description: @@ -232,6 +493,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.5" + js_wrapping: + dependency: transitive + description: + name: js_wrapping + sha256: e385980f7c76a8c1c9a560dfb623b890975841542471eade630b2871d243851c + url: "https://pub.dev" + source: hosted + version: "0.7.4" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev" + source: hosted + version: "4.8.1" line_awesome_flutter: dependency: "direct main" description: @@ -248,6 +525,70 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" + local_auth: + dependency: "direct main" + description: + name: local_auth + sha256: "0cf238be2bfa51a6c9e7e9cfc11c05ea39f2a3a4d3e5bb255d0ebc917da24401" + url: "https://pub.dev" + source: hosted + version: "2.1.6" + local_auth_android: + dependency: transitive + description: + name: local_auth_android + sha256: "523dd636ce061ddb296cbc3db410cb8f21efb7d8798f7b9532c8038ce2f8bad5" + url: "https://pub.dev" + source: hosted + version: "1.0.31" + local_auth_ios: + dependency: transitive + description: + name: local_auth_ios + sha256: edc2977c5145492f3451db9507a2f2f284ee4f408950b3e16670838726761940 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + local_auth_platform_interface: + dependency: transitive + description: + name: local_auth_platform_interface + sha256: "9e160d59ef0743e35f1b50f4fb84dc64f55676b1b8071e319ef35e7f3bc13367" + url: "https://pub.dev" + source: hosted + version: "1.0.7" + local_auth_windows: + dependency: transitive + description: + name: local_auth_windows + sha256: "19323b75ab781d5362dbb15dcb7e0916d2431c7a6dbdda016ec9708689877f73" + url: "https://pub.dev" + source: hosted + version: "1.0.8" + location: + dependency: "direct main" + description: + name: location + sha256: "153c5d779e3bfe293ba326105522ab3e3971f11390b32c7e4dce804aa23655dc" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + location_platform_interface: + dependency: transitive + description: + name: location_platform_interface + sha256: "7ef30443cffadad54d534dfd370fe126bc087655aaa53f181570b9812a87a806" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + location_web: + dependency: transitive + description: + name: location_web + sha256: "00624210b3461976d868471b3e136886a0e166eebe09db636e7d0a4057648fbb" + url: "https://pub.dev" + source: hosted + version: "4.0.0" matcher: dependency: transitive description: @@ -272,6 +613,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.0" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + sha256: ceb027f6bc6a60674a233b4a90a7658af1aebdea833da0b5b53c1e9821a78c7b + url: "https://pub.dev" + source: hosted + version: "4.0.2" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" + url: "https://pub.dev" + source: hosted + version: "2.0.1" path: dependency: transitive description: @@ -384,6 +741,30 @@ packages: url: "https://pub.dev" source: hosted version: "4.2.4" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "2ef8b4e91cb3b55d155e0e34eeae0ac7107974e451495c955ac04ddee8cc21fd" + url: "https://pub.dev" + source: hosted + version: "0.26.0" + sanitize_html: + dependency: transitive + description: + name: sanitize_html + sha256: "0a445f19bbaa196f5a4f93461aa066b94e6e025622eb1e9bc77872a5e25233a5" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + scrollable_positioned_list: + dependency: transitive + description: + name: scrollable_positioned_list + sha256: "1b54d5f1329a1e263269abc9e2543d90806131aa14fe7c6062a8054d57249287" + url: "https://pub.dev" + source: hosted + version: "0.3.8" sky_engine: dependency: transitive description: flutter @@ -413,6 +794,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" string_scanner: dependency: transitive description: @@ -461,6 +850,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + weekday_selector: + dependency: "direct main" + description: + name: weekday_selector + sha256: "783954997aa30a8890b24196784543752dbf179282ca4d9139ecd70d63fea99e" + url: "https://pub.dev" + source: hosted + version: "1.1.0" win32: dependency: transitive description: diff --git a/ride_share/pubspec.yaml b/ride_share/pubspec.yaml index 7f48437..a91d672 100644 --- a/ride_share/pubspec.yaml +++ b/ride_share/pubspec.yaml @@ -43,6 +43,22 @@ dependencies: flutter_otp_text_field: ^1.1.1 adobe_xd: ^2.0.1 flutter_svg: ^1.1.6 + google_maps_flutter: ^2.3.0 + fl_country_code_picker: ^0.1.4 + firebase_core: ^2.14.0 + firebase_auth: ^4.6.3 + cloud_firestore: ^4.8.1 + firebase_database: ^10.2.3 + intl: ^0.17.0 + local_auth: ^2.1.6 + dbcrypt: ^2.0.0 + email_auth: ^1.0.0 + weekday_selector: ^1.1.0 + geocoding: ^2.1.0 + flutter_google_places: ^0.3.0 + google_maps_webservice: ^0.0.20-nullsafety.5 + flutter_polyline_points: ^1.0.0 + location: ^5.0.0 dev_dependencies: flutter_test: @@ -78,6 +94,7 @@ flutter: # the material Icons class. uses-material-design: true assets: + - assets/ - assets/images/ # To add assets to your application, add an assets section, like this: diff --git a/ride_share/windows/flutter/generated_plugin_registrant.cc b/ride_share/windows/flutter/generated_plugin_registrant.cc index 8b6d468..2eb49be 100644 --- a/ride_share/windows/flutter/generated_plugin_registrant.cc +++ b/ride_share/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,12 @@ #include "generated_plugin_registrant.h" +#include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + FirebaseCorePluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); + LocalAuthPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("LocalAuthPlugin")); } diff --git a/ride_share/windows/flutter/generated_plugins.cmake b/ride_share/windows/flutter/generated_plugins.cmake index b93c4c3..682abe8 100644 --- a/ride_share/windows/flutter/generated_plugins.cmake +++ b/ride_share/windows/flutter/generated_plugins.cmake @@ -3,6 +3,8 @@ # list(APPEND FLUTTER_PLUGIN_LIST + firebase_core + local_auth_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST