├── analysis_options.yaml ├── matic.png ├── ios ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme └── .gitignore ├── assets ├── voter.png ├── trophy.png ├── winning.png ├── 404-error.png ├── ballot-box.png └── colored-ballot-box.png ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ └── Icon-512.png ├── manifest.json └── index.html ├── lib ├── features │ ├── auth │ │ ├── presentation │ │ │ ├── bloc │ │ │ │ ├── login_bloc │ │ │ │ │ ├── login_barrel_file.dart │ │ │ │ │ ├── validators.dart │ │ │ │ │ ├── login_bloc_event.dart │ │ │ │ │ ├── login_bloc.dart │ │ │ │ │ └── login_bloc_state.dart │ │ │ │ ├── auth_bloc │ │ │ │ │ ├── auth_barrel_bloc.dart │ │ │ │ │ ├── auth_events.dart │ │ │ │ │ ├── auth_states.dart │ │ │ │ │ └── auth_bloc.dart │ │ │ │ └── register_bloc │ │ │ │ │ ├── register_event.dart │ │ │ │ │ ├── register_bloc.dart │ │ │ │ │ └── register_state.dart │ │ │ ├── screens │ │ │ │ ├── splash_screen.dart │ │ │ │ ├── register_screen.dart │ │ │ │ ├── Login_Screen.dart │ │ │ │ ├── register_form.dart │ │ │ │ └── Login_Form.dart │ │ │ └── widgets │ │ │ │ ├── login_button.dart │ │ │ │ ├── Register_button.dart │ │ │ │ └── create_account_button.dart │ │ └── data │ │ │ └── user_repository.dart │ └── ui │ │ ├── bloc │ │ ├── user_bloc │ │ │ ├── user_bloc_event.dart │ │ │ ├── user_bloc_state.dart │ │ │ └── user_bloc_bloc.dart │ │ ├── voter_bloc │ │ │ ├── voter_event.dart │ │ │ ├── voter_state.dart │ │ │ └── voter_bloc.dart │ │ └── admin_bloc │ │ │ ├── admin_event.dart │ │ │ ├── admin_state.dart │ │ │ └── admin_bloc.dart │ │ └── screens │ │ ├── admin_screens │ │ ├── admin_dashboard.dart │ │ └── dashboard_page.dart │ │ └── voter_screens │ │ ├── voter_dashboard.dart │ │ ├── voter_dashboard_page.dart │ │ ├── delegate_voter.dart │ │ ├── voter_profile.dart │ │ └── election_results_page.dart ├── backend │ ├── errors.dart │ └── remote_datasource.dart ├── models │ ├── election_model.dart │ ├── candidate_model.dart │ └── voter_model.dart ├── home_screen.dart ├── database │ └── firestore_repository.dart └── main.dart ├── screenshots ├── login_screen.jpg ├── vote_screen.jpg ├── voters_screen.jpg ├── results_screen.jpg ├── add_voter_screen.jpg ├── candidates_screen.jpg ├── add_candidate_screen.jpg ├── delegate_vote_screen.jpg ├── registration_screen.jpg ├── voter_profile_screen.jpg ├── add_voter_txhash_screen.jpg ├── admin_dashboard_screen.jpg ├── profile_post_delegation.jpg ├── self_delegation_error.jpg ├── voter_dashboard_screen.jpg ├── add_voter_loading_screen.jpg ├── voter's_candidates_screen.jpg ├── voter_profile_post_voting.jpg ├── election_ongoing_vote_error.jpg ├── ongoing_election_admin_dashboard.jpg └── concluded_election_admin_dashboard.jpg ├── EVote_Architecture_Diagram.png ├── android ├── gradle.properties ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── drawable-hdpi │ │ │ │ │ ├── ic_launcher_background.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── drawable-mdpi │ │ │ │ │ ├── ic_launcher_background.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── drawable-xhdpi │ │ │ │ │ ├── ic_launcher_background.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── drawable-xxhdpi │ │ │ │ │ ├── ic_launcher_background.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── drawable-xxxhdpi │ │ │ │ │ ├── ic_launcher_background.png │ │ │ │ │ └── ic_launcher_foreground.png │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ │ └── ic_launcher.xml │ │ │ │ ├── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ └── values │ │ │ │ │ └── styles.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── e_vote │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── google-services.json │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .gitignore ├── .settings │ └── org.eclipse.buildship.core.prefs ├── settings.gradle ├── .project └── build.gradle ├── .metadata ├── .gitignore ├── pubspec.yaml ├── README.md └── contracts └── Election.sol /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /matic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/matic.png -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /assets/voter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/assets/voter.png -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/web/favicon.png -------------------------------------------------------------------------------- /assets/trophy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/assets/trophy.png -------------------------------------------------------------------------------- /assets/winning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/assets/winning.png -------------------------------------------------------------------------------- /assets/404-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/assets/404-error.png -------------------------------------------------------------------------------- /assets/ballot-box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/assets/ballot-box.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/web/icons/Icon-512.png -------------------------------------------------------------------------------- /assets/colored-ballot-box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/assets/colored-ballot-box.png -------------------------------------------------------------------------------- /lib/features/auth/presentation/bloc/login_bloc/login_barrel_file.dart: -------------------------------------------------------------------------------- 1 | export 'login_bloc.dart'; 2 | 3 | -------------------------------------------------------------------------------- /screenshots/login_screen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/screenshots/login_screen.jpg -------------------------------------------------------------------------------- /screenshots/vote_screen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/screenshots/vote_screen.jpg -------------------------------------------------------------------------------- /screenshots/voters_screen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/screenshots/voters_screen.jpg -------------------------------------------------------------------------------- /EVote_Architecture_Diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/EVote_Architecture_Diagram.png -------------------------------------------------------------------------------- /screenshots/results_screen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/screenshots/results_screen.jpg -------------------------------------------------------------------------------- /screenshots/add_voter_screen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/screenshots/add_voter_screen.jpg -------------------------------------------------------------------------------- /screenshots/candidates_screen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/screenshots/candidates_screen.jpg -------------------------------------------------------------------------------- /screenshots/add_candidate_screen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/screenshots/add_candidate_screen.jpg -------------------------------------------------------------------------------- /screenshots/delegate_vote_screen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/screenshots/delegate_vote_screen.jpg -------------------------------------------------------------------------------- /screenshots/registration_screen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/screenshots/registration_screen.jpg -------------------------------------------------------------------------------- /screenshots/voter_profile_screen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/screenshots/voter_profile_screen.jpg -------------------------------------------------------------------------------- /screenshots/add_voter_txhash_screen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/screenshots/add_voter_txhash_screen.jpg -------------------------------------------------------------------------------- /screenshots/admin_dashboard_screen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/screenshots/admin_dashboard_screen.jpg -------------------------------------------------------------------------------- /screenshots/profile_post_delegation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/screenshots/profile_post_delegation.jpg -------------------------------------------------------------------------------- /screenshots/self_delegation_error.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/screenshots/self_delegation_error.jpg -------------------------------------------------------------------------------- /screenshots/voter_dashboard_screen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/screenshots/voter_dashboard_screen.jpg -------------------------------------------------------------------------------- /screenshots/add_voter_loading_screen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/screenshots/add_voter_loading_screen.jpg -------------------------------------------------------------------------------- /screenshots/voter's_candidates_screen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/screenshots/voter's_candidates_screen.jpg -------------------------------------------------------------------------------- /screenshots/voter_profile_post_voting.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/screenshots/voter_profile_post_voting.jpg -------------------------------------------------------------------------------- /screenshots/election_ongoing_vote_error.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/screenshots/election_ongoing_vote_error.jpg -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /screenshots/ongoing_election_admin_dashboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/screenshots/ongoing_election_admin_dashboard.jpg -------------------------------------------------------------------------------- /screenshots/concluded_election_admin_dashboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/screenshots/concluded_election_admin_dashboard.jpg -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/android/app/src/main/res/drawable-hdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/android/app/src/main/res/drawable-mdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/android/app/src/main/res/drawable-xhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dhiraj03/EVote/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /lib/features/auth/presentation/bloc/auth_bloc/auth_barrel_bloc.dart: -------------------------------------------------------------------------------- 1 | export '../auth_bloc/auth_bloc.dart'; 2 | 3 | export '../auth_bloc/auth_events.dart'; 4 | export '../auth_bloc/auth_states.dart'; -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/e_vote/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.e_vote 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /lib/backend/errors.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class ErrorMessage extends Equatable { 5 | final String message; 6 | ErrorMessage({@required this.message}); 7 | @override 8 | List get props => [message]; 9 | } 10 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/features/auth/presentation/screens/splash_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SplashScreen extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Scaffold( 7 | body: Center(child: Text('Splash Screen')), 8 | ); 9 | } 10 | } -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/features/ui/bloc/user_bloc/user_bloc_event.dart: -------------------------------------------------------------------------------- 1 | part of 'user_bloc_bloc.dart'; 2 | 3 | abstract class UserBlocEvent extends Equatable { 4 | const UserBlocEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class IdentifyUser extends UserBlocEvent { 11 | @override 12 | List get props => []; 13 | } 14 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: e64fe11542238376fdfbf351d81e22d08296da45 8 | channel: master 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/features/ui/bloc/user_bloc/user_bloc_state.dart: -------------------------------------------------------------------------------- 1 | part of 'user_bloc_bloc.dart'; 2 | 3 | abstract class UserBlocState extends Equatable { 4 | const UserBlocState(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class UserBlocInitial extends UserBlocState {} 11 | 12 | class VoterState extends UserBlocState {} 13 | 14 | class Admin extends UserBlocState {} 15 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /android/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | arguments= 2 | auto.sync=false 3 | build.scans.enabled=false 4 | connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) 5 | connection.project.dir= 6 | eclipse.preferences.version=1 7 | gradle.user.home= 8 | java.home=D\:/JDK_11 9 | jvm.arguments= 10 | offline.mode=false 11 | override.workspace.settings=true 12 | show.console.view=true 13 | show.executions.view=true 14 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } -------------------------------------------------------------------------------- /lib/features/auth/presentation/bloc/login_bloc/validators.dart: -------------------------------------------------------------------------------- 1 | class Validators { 2 | static final _emailRegExp = RegExp( 3 | r'^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$', 4 | ); 5 | 6 | static final RegExp _passwordRegExp = RegExp( 7 | r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$', 8 | ); 9 | 10 | static bool isValidEmail(String email) { 11 | return _emailRegExp.hasMatch(email); 12 | } 13 | 14 | static bool isValidPassword(String password) { 15 | return _passwordRegExp.hasMatch(password); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /android/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | android______________________ 4 | Project android______________________ created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.buildship.core.gradleprojectbuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.buildship.core.gradleprojectnature 16 | 17 | 18 | -------------------------------------------------------------------------------- /lib/features/auth/presentation/widgets/login_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class LoginButton extends StatelessWidget { 4 | final VoidCallback _onPressed; 5 | 6 | LoginButton({Key key, VoidCallback onPressed}) 7 | : _onPressed = onPressed, 8 | super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return RaisedButton( 13 | shape: RoundedRectangleBorder( 14 | borderRadius: BorderRadius.circular(30.0), 15 | ), 16 | color: Color(0xFFf4511e), 17 | onPressed: _onPressed, 18 | child: Text('Login'), 19 | ); 20 | } 21 | } -------------------------------------------------------------------------------- /lib/features/auth/presentation/widgets/Register_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class RegisterButton extends StatelessWidget { 4 | final VoidCallback _onPressed; 5 | 6 | RegisterButton({Key key, VoidCallback onPressed}) 7 | : _onPressed = onPressed, 8 | super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return RaisedButton( 13 | shape: RoundedRectangleBorder( 14 | borderRadius: BorderRadius.circular(30.0), 15 | ), 16 | color: Color(0xFFf4511e), 17 | onPressed: _onPressed, 18 | child: Text('Register'), 19 | ); 20 | } 21 | } -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "e_vote", 3 | "short_name": "e_vote", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | classpath 'com.google.gms:google-services:4.3.3' 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | rootProject.buildDir = '../build' 23 | subprojects { 24 | project.buildDir = "${rootProject.buildDir}/${project.name}" 25 | } 26 | subprojects { 27 | project.evaluationDependsOn(':app') 28 | } 29 | 30 | task clean(type: Delete) { 31 | delete rootProject.buildDir 32 | } 33 | -------------------------------------------------------------------------------- /lib/features/auth/presentation/bloc/auth_bloc/auth_events.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:meta/meta.dart'; 3 | 4 | @immutable 5 | abstract class AuthenticationEvent extends Equatable { 6 | AuthenticationEvent([List props = const []]) : super(); 7 | } 8 | 9 | class AppStarted extends AuthenticationEvent { 10 | @override 11 | String toString() => 'AppStarted'; 12 | 13 | @override 14 | List get props => []; 15 | } 16 | 17 | class LoggedIn extends AuthenticationEvent { 18 | @override 19 | String toString() => 'LoggedIn'; 20 | 21 | @override 22 | List get props => []; 23 | } 24 | 25 | class LoggedOut extends AuthenticationEvent { 26 | @override 27 | List get props => []; 28 | @override 29 | String toString() => 'LoggedOut'; 30 | } -------------------------------------------------------------------------------- /lib/models/election_model.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:e_vote/features/ui/bloc/user_bloc/user_bloc_bloc.dart'; 3 | import 'package:e_vote/models/candidate_model.dart'; 4 | import 'package:e_vote/models/voter_model.dart'; 5 | import 'package:equatable/equatable.dart'; 6 | import 'package:flutter/material.dart'; 7 | 8 | class ElectionModel extends Equatable { 9 | final String adminAddress; 10 | final String description; 11 | final int candidateCount; 12 | List candidates; 13 | List voters; 14 | Candidate winner; 15 | ElectionModel( 16 | {@required this.adminAddress, 17 | this.candidateCount, 18 | this.candidates, 19 | @required this.description, 20 | this.voters, 21 | this.winner}); 22 | List get props => 23 | [adminAddress, description, candidateCount, candidates, voters, winner]; 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Exceptions to above rules. 44 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 45 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/models/candidate_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class Candidate extends Equatable { 5 | final String address; 6 | final int id; 7 | final int count; 8 | final String name; 9 | final String proposal; 10 | Candidate({this.count, this.address, this.name, this.id, this.proposal}); 11 | List get props => [address, count, name, id, proposal]; 12 | 13 | factory Candidate.fromJson(Map json) { 14 | return Candidate( 15 | id: json["id"], name: json["name"], proposal: json["proposal"]); 16 | } 17 | 18 | factory Candidate.result(Map json) { 19 | return Candidate(id: json["id"], name: json["name"], count: json["count"]); 20 | } 21 | 22 | factory Candidate.winner(Map json) { 23 | return Candidate(id: json["id"], name: json["name"], count: json["votes"]); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/features/auth/presentation/bloc/auth_bloc/auth_states.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | @immutable 5 | abstract class AuthState extends Equatable { 6 | AuthState([List props = const []]) : super(); 7 | } 8 | 9 | class Uninitialized extends AuthState { 10 | @override 11 | List get props => []; 12 | 13 | @override 14 | String toString() => 'Uninitialized'; 15 | } 16 | 17 | class Authenticated extends AuthState { 18 | @override 19 | List get props => [displayName]; 20 | 21 | final String displayName; 22 | Authenticated(this.displayName); 23 | 24 | @override 25 | String toString() => 'Authenticated {display name : $displayName}'; 26 | 27 | } 28 | 29 | class Unauthenticated extends AuthState { 30 | @override 31 | List get props => []; 32 | 33 | @override 34 | String toString() => 'Unauthenticated'; 35 | } 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /lib/features/auth/presentation/widgets/create_account_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:e_vote/features/auth/data/user_repository.dart'; 2 | import 'package:e_vote/features/auth/presentation/screens/register_screen.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class CreateAccountButton extends StatelessWidget { 6 | final UserRepository _userRepository; 7 | 8 | CreateAccountButton({Key key, @required UserRepository userRepository}) 9 | : assert(userRepository != null), 10 | _userRepository = userRepository, 11 | super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return FlatButton( 16 | child: Text( 17 | 'Create an Account', 18 | ), 19 | onPressed: () { 20 | Navigator.of(context).push( 21 | MaterialPageRoute(builder: (context) { 22 | return RegisterScreen(userRepository: _userRepository); 23 | }), 24 | ); 25 | }, 26 | ); 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /lib/features/ui/bloc/user_bloc/user_bloc_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:bloc/bloc.dart'; 3 | import 'package:e_vote/database/firestore_repository.dart'; 4 | import 'package:e_vote/features/auth/data/user_repository.dart'; 5 | import 'package:equatable/equatable.dart'; 6 | import 'package:flutter/material.dart'; 7 | 8 | part 'user_bloc_event.dart'; 9 | part 'user_bloc_state.dart'; 10 | 11 | class UserBlocBloc extends Bloc { 12 | UserBlocState get initialState => UserBlocInitial(); 13 | final FirestoreRepository repo = FirestoreRepository(); 14 | final UserRepository userRepository = UserRepository(); 15 | @override 16 | Stream mapEventToState( 17 | UserBlocEvent event, 18 | ) async* { 19 | if (event is IdentifyUser) { 20 | final String email = await userRepository.getUserEmail(); 21 | if (await repo.isVoter(email) == true) 22 | yield VoterState(); 23 | else 24 | yield Admin(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /lib/features/auth/presentation/bloc/register_bloc/register_event.dart: -------------------------------------------------------------------------------- 1 | part of 'register_bloc.dart'; 2 | 3 | abstract class RegisterEvent extends Equatable { 4 | const RegisterEvent(); 5 | } 6 | 7 | class EmailChanged extends RegisterEvent { 8 | final String email; 9 | EmailChanged({@required this.email}); 10 | @override 11 | List get props => [email]; 12 | 13 | @override 14 | String toString() => 'Email Changed : {email : $email}'; 15 | } 16 | 17 | class PasswordChanged extends RegisterEvent { 18 | final String password; 19 | PasswordChanged({@required this.password}); 20 | @override 21 | String toString() => 'Password Changed : {password : $password}'; 22 | @override 23 | List get props => [password]; 24 | } 25 | 26 | class Submitted extends RegisterEvent { 27 | final String email; 28 | final String password; 29 | 30 | Submitted({@required this.email, @required this.password}) 31 | : super(); 32 | 33 | @override 34 | String toString() { 35 | return 'Submitted { email: $email, password: $password }'; 36 | } 37 | 38 | @override 39 | List get props => [email, password]; 40 | } 41 | 42 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | e_vote 18 | 19 | 20 | 21 | 24 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /lib/models/voter_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class Voter extends Equatable { 5 | final int id; 6 | final String name; 7 | final String address; 8 | final bool hasVoted; 9 | final int weight; 10 | final int voteTowards; 11 | String delegateAddress; 12 | Voter( 13 | {this.id, 14 | this.delegateAddress, 15 | @required this.address, 16 | this.hasVoted, 17 | this.name, 18 | this.voteTowards, 19 | @required this.weight}); 20 | 21 | List get props => 22 | [address, hasVoted, name, voteTowards, delegateAddress, weight]; 23 | 24 | factory Voter.fromJson(Map json) { 25 | return Voter( 26 | id: json["id"], 27 | delegateAddress: json["delegate"], 28 | weight: json["weight"], 29 | address: json["voterAddress"]); 30 | } 31 | 32 | factory Voter.profileJson(Map json, String voterAddress) { 33 | return Voter( 34 | address: voterAddress, 35 | weight: json["weight"], 36 | delegateAddress: json["delegate"], 37 | name: json["name"], 38 | voteTowards: json["votedTowards"], 39 | id: json["id"] 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/features/ui/bloc/voter_bloc/voter_event.dart: -------------------------------------------------------------------------------- 1 | part of 'voter_bloc.dart'; 2 | 3 | abstract class VoterEvent extends Equatable { 4 | const VoterEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class ShowElectionDetails extends VoterEvent { 11 | @override 12 | List get props => []; 13 | } 14 | 15 | class DisplayCandidates extends VoterEvent { 16 | @override 17 | List get props => []; 18 | } 19 | 20 | class Vote extends VoterEvent { 21 | final int candidateID; 22 | Vote({@required this.candidateID}); 23 | @override 24 | List get props => [candidateID]; 25 | } 26 | 27 | class DelegateVote extends VoterEvent { 28 | final String delegateAddress; 29 | DelegateVote({@required this.delegateAddress}); 30 | @override 31 | List get props => [delegateAddress]; 32 | } 33 | 34 | class ShowWinner extends VoterEvent { 35 | @override 36 | List get props => []; 37 | } 38 | 39 | class ShowResults extends VoterEvent { 40 | @override 41 | List get props => []; 42 | } 43 | 44 | class ElectionDetails extends VoterEvent { 45 | @override 46 | List get props => []; 47 | } 48 | 49 | class GetVoterProfile extends VoterEvent { 50 | @override 51 | List get props => []; 52 | } 53 | -------------------------------------------------------------------------------- /lib/features/auth/presentation/screens/register_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:e_vote/features/auth/data/user_repository.dart'; 2 | import 'package:e_vote/features/auth/presentation/bloc/register_bloc/register_bloc.dart'; 3 | import 'package:e_vote/features/auth/presentation/screens/register_form.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | 7 | class RegisterScreen extends StatefulWidget { 8 | final UserRepository _userRepository; 9 | 10 | RegisterScreen({Key key, @required UserRepository userRepository}) 11 | : assert(userRepository != null), 12 | _userRepository = userRepository, 13 | super(key: key); 14 | 15 | State createState() => _RegisterScreenState(); 16 | } 17 | 18 | class _RegisterScreenState extends State { 19 | RegisterBloc _registerBloc; 20 | 21 | @override 22 | void initState() { 23 | super.initState(); 24 | _registerBloc = RegisterBloc( 25 | userRepository: widget._userRepository, 26 | ); 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return Scaffold( 32 | body: Center( 33 | child: BlocProvider( 34 | create: (_) => _registerBloc, 35 | child: RegisterForm(), 36 | ), 37 | ), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/features/ui/bloc/admin_bloc/admin_event.dart: -------------------------------------------------------------------------------- 1 | part of 'admin_bloc.dart'; 2 | 3 | abstract class AdminEvent extends Equatable { 4 | const AdminEvent(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class StartElection extends AdminEvent { 11 | @override 12 | List get props => []; 13 | } 14 | 15 | class EndElection extends AdminEvent { 16 | @override 17 | List get props => []; 18 | } 19 | 20 | class DisplayCandidates extends AdminEvent { 21 | @override 22 | List get props => []; 23 | } 24 | 25 | class AddCandidate extends AdminEvent { 26 | final String name; 27 | final String proposal; 28 | AddCandidate({@required this.name, @required this.proposal}); 29 | @override 30 | List get props => [name, proposal]; 31 | } 32 | 33 | class DisplayVoters extends AdminEvent { 34 | @override 35 | List get props => []; 36 | } 37 | 38 | class AddVoter extends AdminEvent { 39 | final String voterAddress; 40 | AddVoter({@required this.voterAddress}); 41 | @override 42 | List get props => [voterAddress]; 43 | } 44 | 45 | 46 | 47 | class ShowResults extends AdminEvent { 48 | @override 49 | List get props => []; 50 | } 51 | 52 | class GetElectionDetails extends AdminEvent { 53 | @override 54 | List get props => []; 55 | } 56 | -------------------------------------------------------------------------------- /lib/features/auth/presentation/screens/Login_Screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:e_vote/features/auth/data/user_repository.dart'; 2 | import 'package:e_vote/features/auth/presentation/bloc/login_bloc/login_bloc.dart'; 3 | import 'package:e_vote/features/auth/presentation/screens/Login_Form.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | 7 | class LoginScreen extends StatefulWidget { 8 | final UserRepository _userRepository; 9 | 10 | //The LoginScreen also receives the instance of the UserRepository from a parent class. 11 | LoginScreen({Key key, @required UserRepository userRepository}) 12 | : assert(userRepository != null), 13 | _userRepository = userRepository, 14 | super(key: key); 15 | 16 | State createState() => _LoginScreenState(); 17 | } 18 | 19 | class _LoginScreenState extends State { 20 | LoginBloc _loginBloc; 21 | 22 | UserRepository get _userRepository => widget._userRepository; 23 | 24 | @override 25 | void initState() { 26 | super.initState(); 27 | _loginBloc = LoginBloc( 28 | userRepository: _userRepository, 29 | ); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | return Scaffold( 35 | body: BlocProvider( 36 | create: (_) => _loginBloc, 37 | child: LoginForm(userRepository: _userRepository), 38 | ), 39 | ); 40 | } 41 | 42 | @override 43 | void dispose() { 44 | _loginBloc.close(); 45 | super.dispose(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /android/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "559344566999", 4 | "firebase_url": "https://e-vote-a0794.firebaseio.com", 5 | "project_id": "e-vote-a0794", 6 | "storage_bucket": "e-vote-a0794.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:559344566999:android:cc1585426c19fbe6ee2692", 12 | "android_client_info": { 13 | "package_name": "com.acm.evote" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "559344566999-fa4n3vhstdrie5kvao1h6jvfjdh2p1co.apps.googleusercontent.com", 19 | "client_type": 1, 20 | "android_info": { 21 | "package_name": "com.acm.evote", 22 | "certificate_hash": "e308625c1b76967b6d6b97ee53d6cba380117002" 23 | } 24 | }, 25 | { 26 | "client_id": "559344566999-70lolro9nl2tr1g1cgd858lpbkin07hp.apps.googleusercontent.com", 27 | "client_type": 3 28 | } 29 | ], 30 | "api_key": [ 31 | { 32 | "current_key": "AIzaSyD5AAywvVePNfbRdUmv9lvjXn1BxKyRWoE" 33 | } 34 | ], 35 | "services": { 36 | "appinvite_service": { 37 | "other_platform_oauth_client": [ 38 | { 39 | "client_id": "559344566999-70lolro9nl2tr1g1cgd858lpbkin07hp.apps.googleusercontent.com", 40 | "client_type": 3 41 | } 42 | ] 43 | } 44 | } 45 | } 46 | ], 47 | "configuration_version": "1" 48 | } -------------------------------------------------------------------------------- /lib/features/auth/presentation/bloc/auth_bloc/auth_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:e_vote/features/auth/data/user_repository.dart'; 2 | import 'package:e_vote/features/auth/presentation/bloc/auth_bloc/auth_events.dart'; 3 | import 'package:e_vote/features/auth/presentation/bloc/auth_bloc/auth_states.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | 7 | class AuthBloc extends Bloc { 8 | AuthBloc({@required this.repository}) : super(); 9 | final UserRepository repository; 10 | 11 | @override 12 | AuthState get initialState => Uninitialized(); 13 | 14 | @override 15 | Stream mapEventToState(AuthenticationEvent event) async* { 16 | if (event is AppStarted) 17 | yield* mapAppStarted(); 18 | else if (event is LoggedIn) 19 | yield* mapLoggedIn(); 20 | else if (event is LoggedOut) yield* mapLoggedOut(); 21 | } 22 | 23 | Stream mapAppStarted() async* { 24 | try { 25 | final isSignedIn = await repository.isSignedIn(); 26 | if (isSignedIn) { 27 | final name = await repository.getUser(); 28 | yield Authenticated(name); 29 | } else { 30 | yield Unauthenticated(); 31 | } 32 | } catch (e) { 33 | yield Unauthenticated(); 34 | } 35 | } 36 | 37 | Stream mapLoggedIn() async* { 38 | yield Authenticated(await repository.getUser()); 39 | } 40 | 41 | Stream mapLoggedOut() async* { 42 | yield Unauthenticated(); 43 | repository.signOut(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | e_vote 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /lib/features/ui/bloc/admin_bloc/admin_state.dart: -------------------------------------------------------------------------------- 1 | part of 'admin_bloc.dart'; 2 | 3 | abstract class AdminState extends Equatable { 4 | const AdminState(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class AdminInitial extends AdminState {} 11 | 12 | class CandidatesList extends AdminState { 13 | final List candidates; 14 | CandidatesList({@required this.candidates}); 15 | @override 16 | List get props => []; 17 | } 18 | 19 | class VotersList extends AdminState { 20 | final List voters; 21 | VotersList({@required this.voters}); 22 | @override 23 | List get props => []; 24 | } 25 | 26 | class ElectionDetailsState extends AdminState { 27 | final String description; 28 | final String adminAddress; 29 | final String electionState; 30 | ElectionDetailsState( 31 | {@required this.adminAddress, 32 | @required this.description, 33 | @required this.electionState}); 34 | @override 35 | List get props => [adminAddress, description, electionState]; 36 | } 37 | 38 | class ElectionTxHash extends AdminState { 39 | final String txHash; 40 | ElectionTxHash({@required this.txHash}); 41 | @override 42 | List get props => [txHash]; 43 | } 44 | 45 | class AdminError extends AdminState { 46 | final ErrorMessage errorMessage; 47 | AdminError({@required this.errorMessage}); 48 | @override 49 | List get props => [errorMessage]; 50 | } 51 | 52 | class Loading extends AdminState { 53 | @override 54 | List get props => []; 55 | } 56 | 57 | class Results extends AdminState { 58 | final List results; 59 | final Candidate winner; 60 | Results({@required this.results, @required this.winner}); 61 | @override 62 | List get props => [results, winner]; 63 | } 64 | -------------------------------------------------------------------------------- /lib/home_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:e_vote/database/firestore_repository.dart'; 2 | import 'package:e_vote/features/auth/data/user_repository.dart'; 3 | import 'package:e_vote/features/auth/presentation/bloc/auth_bloc/auth_bloc.dart'; 4 | import 'package:e_vote/features/auth/presentation/bloc/auth_bloc/auth_events.dart'; 5 | import 'package:e_vote/features/ui/bloc/user_bloc/user_bloc_bloc.dart'; 6 | import 'package:e_vote/features/ui/screens/admin_screens/admin_dashboard.dart'; 7 | import 'package:e_vote/features/ui/screens/voter_screens/voter_dashboard.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_bloc/flutter_bloc.dart'; 10 | 11 | class HomeScreen extends StatefulWidget { 12 | final UserRepository userRepository; 13 | HomeScreen({Key key, @required this.userRepository}) : super(key: key); 14 | @override 15 | _HomeScreenState createState() => _HomeScreenState(); 16 | } 17 | 18 | class _HomeScreenState extends State { 19 | final FirestoreRepository firestoreRepository = FirestoreRepository(); 20 | final UserBlocBloc userBloc = UserBlocBloc(); 21 | @override 22 | Widget build(BuildContext context) { 23 | return Scaffold( 24 | body: BlocConsumer( 25 | bloc: userBloc..add(IdentifyUser()), 26 | listener: (context, state) {}, 27 | builder: (context, state) { 28 | if (state is VoterState) { 29 | return VoterDashboard(); 30 | } else if (state is Admin) { 31 | return AdminDashboard(); 32 | } 33 | return Center( 34 | child: CircularProgressIndicator( 35 | valueColor: 36 | new AlwaysStoppedAnimation(Theme.of(context).primaryColor), 37 | )); 38 | }, 39 | )); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/features/auth/presentation/bloc/login_bloc/login_bloc_event.dart: -------------------------------------------------------------------------------- 1 | part of 'login_bloc.dart'; 2 | 3 | abstract class LoginEvent extends Equatable { 4 | LoginEvent([List props = const []]) : super(); 5 | } 6 | 7 | class EmailChanged extends LoginEvent { 8 | final String email; 9 | EmailChanged({@required this.email}) : super(); 10 | 11 | @override 12 | List get props => [email]; 13 | 14 | @override 15 | String toString() => 'Email Changed { email : $email}'; 16 | } 17 | 18 | class PasswordChanged extends LoginEvent { 19 | final String password; 20 | 21 | PasswordChanged({@required this.password}) : super(); 22 | 23 | @override 24 | List get props => [password]; 25 | 26 | @override 27 | String toString() => 'PasswordChanged { password: $password }'; 28 | } 29 | 30 | class Submitted extends LoginEvent { 31 | final String email; 32 | final String password; 33 | 34 | Submitted({@required this.email, @required this.password}) 35 | : super(); 36 | 37 | @override 38 | List get props => [email, password]; 39 | 40 | @override 41 | String toString() { 42 | return 'Submitted { email: $email, password: $password }'; 43 | } 44 | } 45 | 46 | class LoginWithGooglePressed extends LoginEvent { 47 | @override 48 | String toString() => 'LoginWithGooglePressed'; 49 | 50 | @override 51 | List get props => []; 52 | } 53 | 54 | class LoginWithCredentialsPressed extends LoginEvent { 55 | final String email; 56 | final String password; 57 | 58 | LoginWithCredentialsPressed({@required this.email, @required this.password}) 59 | : super(); 60 | 61 | @override 62 | List get props => [email, password]; 63 | 64 | @override 65 | String toString() { 66 | return 'LoginWithCredentialsPressed { email: $email, password: $password }'; 67 | } 68 | } 69 | 70 | -------------------------------------------------------------------------------- /lib/features/ui/bloc/voter_bloc/voter_state.dart: -------------------------------------------------------------------------------- 1 | part of 'voter_bloc.dart'; 2 | 3 | abstract class VoterState extends Equatable { 4 | const VoterState(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class VoterInitial extends VoterState {} 11 | 12 | class Loading extends VoterState { 13 | @override 14 | List get props => []; 15 | } 16 | 17 | class ElectionDetailsState extends VoterState { 18 | final String description; 19 | final String adminAddress; 20 | final String electionState; 21 | ElectionDetailsState( 22 | {@required this.adminAddress, 23 | @required this.description, 24 | @required this.electionState}); 25 | @override 26 | List get props => [adminAddress, description, electionState]; 27 | } 28 | 29 | class ElectionTxHash extends VoterState { 30 | final String txHash; 31 | ElectionTxHash({@required this.txHash}); 32 | @override 33 | List get props => [txHash]; 34 | } 35 | 36 | class VoterError extends VoterState { 37 | final ErrorMessage errorMessage; 38 | VoterError({@required this.errorMessage}); 39 | @override 40 | List get props => [errorMessage]; 41 | } 42 | 43 | class CandidatesList extends VoterState { 44 | final List candidates; 45 | CandidatesList({@required this.candidates}); 46 | @override 47 | List get props => []; 48 | } 49 | 50 | class VoterProfileState extends VoterState { 51 | final Voter voterProfile; 52 | final String address; 53 | VoterProfileState({@required this.voterProfile, @required this.address}); 54 | @override 55 | List get props => [voterProfile, address]; 56 | } 57 | 58 | class Results extends VoterState { 59 | final List results; 60 | final Candidate winner; 61 | Results({@required this.results, @required this.winner}); 62 | @override 63 | List get props => [results, winner]; 64 | } -------------------------------------------------------------------------------- /lib/database/firestore_repository.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | import 'dart:math'; 4 | import 'dart:typed_data'; 5 | 6 | import 'package:cloud_firestore/cloud_firestore.dart'; 7 | import 'package:e_vote/features/auth/data/user_repository.dart'; 8 | import 'package:web3dart/crypto.dart'; 9 | import 'package:web3dart/web3dart.dart'; 10 | 11 | class FirestoreRepository { 12 | static final firestoreRepository = Firestore.instance; 13 | /*The collection that will hold all the data of the users is called 'users'. 14 | Each document under the 'users' collection will reference a user. 15 | */ 16 | 17 | //used to reference the collection of users 18 | final CollectionReference ref = firestoreRepository.collection('users'); 19 | /*Function to create a document for a new user (called when a new user registers) 20 | ENSURE that EMAIL IS UNIQUE (If not, add UID field) 21 | */ 22 | void createNewUser(String email) { 23 | var privateKey = generateNewPrivateKey(Random.secure()); 24 | var uid = ref.add({ 25 | "email": email, 26 | "address": EthereumAddress.fromPublicKey(privateKeyToPublic(privateKey)) 27 | .toString(), 28 | "admin": false, 29 | "hasVoted": false, 30 | "delegate": null 31 | }); 32 | } 33 | 34 | //Function to check whether a user is admin or voter/candidate 35 | Future isVoter(String email) async { 36 | final querySnapshot = 37 | await ref.where('email', isEqualTo: email).getDocuments(); 38 | if (querySnapshot.documents[0].data['admin'] == null || 39 | querySnapshot.documents[0].data['admin'] == false) 40 | return true; 41 | else 42 | return false; 43 | } 44 | 45 | Future getVoterAddress(String email) async { 46 | var doc = await ref.where("email", isEqualTo: email).getDocuments(); 47 | return doc.documents[0].data["address"]; 48 | } 49 | } -------------------------------------------------------------------------------- /lib/features/auth/presentation/bloc/register_bloc/register_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:e_vote/features/auth/data/user_repository.dart'; 5 | import 'package:e_vote/features/auth/presentation/bloc/login_bloc/validators.dart'; 6 | import 'package:equatable/equatable.dart'; 7 | import 'package:flutter/material.dart'; 8 | 9 | 10 | part 'register_event.dart'; 11 | part 'register_state.dart'; 12 | 13 | class RegisterBloc extends Bloc { 14 | final UserRepository _userRepository; 15 | 16 | RegisterBloc({@required UserRepository userRepository}) 17 | : assert(userRepository != null), 18 | _userRepository = userRepository, super(); 19 | 20 | @override 21 | RegisterState get initialState => RegisterState.empty(); 22 | 23 | 24 | 25 | @override 26 | Stream mapEventToState( 27 | RegisterEvent event, 28 | ) async* { 29 | if (event is EmailChanged) { 30 | yield* _mapEmailChangedToState(event.email); 31 | } else if (event is PasswordChanged) { 32 | yield* _mapPasswordChangedToState(event.password); 33 | } else if (event is Submitted) { 34 | yield* _mapFormSubmittedToState(event.email, event.password); 35 | } 36 | } 37 | 38 | Stream _mapEmailChangedToState(String email) async* { 39 | yield state.update( 40 | isEmailValid: Validators.isValidEmail(email), 41 | ); 42 | } 43 | 44 | Stream _mapPasswordChangedToState(String password) async* { 45 | yield state.update( 46 | isPasswordValid: Validators.isValidPassword(password), 47 | ); 48 | } 49 | 50 | Stream _mapFormSubmittedToState( 51 | String email, 52 | String password, 53 | ) async* { 54 | yield RegisterState.loading(); 55 | try { 56 | await _userRepository.signUp( 57 | email, password 58 | ); 59 | yield RegisterState.success(); 60 | } catch (_) { 61 | yield RegisterState.failure(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply plugin: 'com.google.gms.google-services' 27 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 28 | 29 | android { 30 | compileSdkVersion 28 31 | 32 | sourceSets { 33 | main.java.srcDirs += 'src/main/kotlin' 34 | } 35 | 36 | lintOptions { 37 | disable 'InvalidPackage' 38 | } 39 | 40 | defaultConfig { 41 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 42 | applicationId "com.acm.evote" 43 | minSdkVersion 23 44 | targetSdkVersion 28 45 | versionCode flutterVersionCode.toInteger() 46 | versionName flutterVersionName 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | } 65 | -------------------------------------------------------------------------------- /lib/features/auth/presentation/bloc/login_bloc/login_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:bloc/bloc.dart'; 4 | import 'package:e_vote/features/auth/data/user_repository.dart'; 5 | import 'package:e_vote/features/auth/presentation/bloc/login_bloc/validators.dart'; 6 | import 'package:equatable/equatable.dart'; 7 | import 'package:flutter/material.dart'; 8 | 9 | part 'login_bloc_event.dart'; 10 | part 'login_bloc_state.dart'; 11 | 12 | class LoginBloc extends Bloc { 13 | UserRepository _userRepository; 14 | 15 | LoginBloc({ 16 | @required UserRepository userRepository, 17 | }) : assert(userRepository != null), 18 | _userRepository = userRepository, 19 | super(); 20 | 21 | @override 22 | LoginState get initialState => LoginState.empty(); 23 | 24 | @override 25 | Stream mapEventToState(LoginEvent event) async* { 26 | if (event is EmailChanged) { 27 | yield* _mapEmailChangedToState(event.email); 28 | } else if (event is PasswordChanged) { 29 | yield* _mapPasswordChangedToState(event.password); 30 | } else if (event is LoginWithGooglePressed) { 31 | yield* _mapLoginWithGooglePressedToState(); 32 | } else if (event is LoginWithCredentialsPressed) { 33 | yield* _mapLoginWithCredentialsPressedToState( 34 | email: event.email, 35 | password: event.password, 36 | ); 37 | } 38 | } 39 | 40 | Stream _mapEmailChangedToState(String email) async* { 41 | yield state.update( 42 | isEmailValid: Validators.isValidEmail(email), 43 | ); 44 | } 45 | 46 | Stream _mapPasswordChangedToState(String password) async* { 47 | yield state.update( 48 | isPasswordValid: Validators.isValidPassword(password), 49 | ); 50 | } 51 | 52 | Stream _mapLoginWithGooglePressedToState() async* { 53 | try { 54 | await _userRepository.signInWithGoogle(); 55 | yield LoginState.success(); 56 | } catch (_) { 57 | yield LoginState.failure(); 58 | } 59 | } 60 | 61 | Stream _mapLoginWithCredentialsPressedToState({ 62 | String email, 63 | String password, 64 | }) async* { 65 | yield LoginState.loading(); 66 | try { 67 | await _userRepository.signInWithCredentials(email, password); 68 | yield LoginState.success(); 69 | } catch (_) { 70 | yield LoginState.failure(); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/features/ui/screens/admin_screens/admin_dashboard.dart: -------------------------------------------------------------------------------- 1 | import 'package:e_vote/features/ui/bloc/admin_bloc/admin_bloc.dart'; 2 | import 'package:e_vote/features/ui/screens/admin_screens/add_candidate.dart'; 3 | import 'package:e_vote/features/ui/screens/admin_screens/add_voter.dart'; 4 | import 'package:e_vote/features/ui/screens/admin_screens/dashboard_page.dart'; 5 | import 'package:e_vote/features/ui/screens/admin_screens/election_results.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_bloc/flutter_bloc.dart'; 8 | import 'package:flutter_icons/flutter_icons.dart'; 9 | 10 | class AdminDashboard extends StatefulWidget { 11 | @override 12 | _AdminDashboardState createState() => _AdminDashboardState(); 13 | } 14 | 15 | class _AdminDashboardState extends State 16 | with SingleTickerProviderStateMixin { 17 | PageController pageController; 18 | TabController tabController; 19 | AdminBloc adminBloc; 20 | @override 21 | void initState() { 22 | pageController = PageController(initialPage: 2); 23 | tabController = TabController(length: 4, vsync: this, initialIndex: 2); 24 | adminBloc = AdminBloc(); 25 | super.initState(); 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return Scaffold( 31 | body: PageView( 32 | allowImplicitScrolling: true, 33 | onPageChanged: (index) { 34 | tabController.index = index; 35 | }, 36 | controller: pageController, 37 | children: [ 38 | ElectionResultsPage(), 39 | AddCandidateScreen(), 40 | DashboardScreen(), 41 | AddVoterScreen() 42 | ], 43 | ), 44 | bottomNavigationBar: TabBar( 45 | labelColor: Color(0xff373737), 46 | indicatorColor: Theme.of(context).primaryColor, 47 | unselectedLabelColor: Color(0xCC8e8e8e), 48 | controller: tabController, 49 | onTap: (index) { 50 | pageController.jumpToPage(index); 51 | }, 52 | tabs: [ 53 | Tab(icon: ImageIcon(AssetImage("assets/winning.png")),), 54 | Tab(icon: Icon(MaterialIcons.person_add)), 55 | Tab(icon: Icon(MaterialCommunityIcons.view_dashboard)), 56 | Tab(icon: ImageIcon(AssetImage("assets/voter.png"))), 57 | ]) 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/features/ui/screens/voter_screens/voter_dashboard.dart: -------------------------------------------------------------------------------- 1 | import 'package:e_vote/features/ui/bloc/voter_bloc/voter_bloc.dart'; 2 | import 'package:e_vote/features/ui/screens/admin_screens/election_results.dart'; 3 | import 'package:e_vote/features/ui/screens/voter_screens/delegate_voter.dart'; 4 | import 'package:e_vote/features/ui/screens/voter_screens/election_results_page.dart'; 5 | import 'package:e_vote/features/ui/screens/voter_screens/show_candidates.dart'; 6 | import 'package:e_vote/features/ui/screens/voter_screens/voter_dashboard_page.dart'; 7 | import 'package:e_vote/features/ui/screens/voter_screens/voter_profile.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_icons/flutter_icons.dart'; 10 | 11 | class VoterDashboard extends StatefulWidget { 12 | @override 13 | _VoterDashboardState createState() => _VoterDashboardState(); 14 | } 15 | 16 | class _VoterDashboardState extends State 17 | with SingleTickerProviderStateMixin { 18 | PageController pageController; 19 | TabController tabController; 20 | VoterBloc voterBloc; 21 | @override 22 | void initState() { 23 | pageController = PageController(initialPage: 2); 24 | tabController = TabController(length: 5, vsync: this, initialIndex: 2); 25 | voterBloc = VoterBloc(); 26 | super.initState(); 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return Scaffold( 32 | body: PageView( 33 | allowImplicitScrolling: true, 34 | onPageChanged: (index) { 35 | tabController.index = index; 36 | }, 37 | controller: pageController, 38 | children: [ 39 | ElectionResults(), 40 | ShowCandidates(), 41 | VoterProfile(), 42 | VoterDashboardPage(), 43 | DelegateVoterPage() 44 | ], 45 | 46 | ), 47 | bottomNavigationBar: TabBar( 48 | labelColor: Color(0xff373737), 49 | indicatorColor: Theme.of(context).primaryColor, 50 | unselectedLabelColor: Color(0xCC8e8e8e), 51 | controller: tabController, 52 | onTap: (index) { 53 | pageController.jumpToPage(index); 54 | }, 55 | tabs: [ 56 | Tab(icon: ImageIcon(AssetImage("assets/winning.png")),), 57 | Tab(icon: ImageIcon(AssetImage("assets/ballot-box.png"))), 58 | Tab(icon: Icon(Icons.person),), 59 | Tab(icon: Icon(MaterialCommunityIcons.view_dashboard)), 60 | Tab(icon: ImageIcon(AssetImage("assets/voter.png"))), 61 | ]), 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lib/features/auth/data/user_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:e_vote/database/firestore_repository.dart'; 2 | import 'package:firebase_auth/firebase_auth.dart'; 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:google_sign_in/google_sign_in.dart'; 5 | 6 | /* if FirebaseAuth/ GoogleSignIn are not injected into an instance of UserRepository, 7 | then we instantiate them internally - allows us to inject mock instances so that we can easily test the 8 | UserRepository 9 | */ 10 | class UserRepository { 11 | UserRepository({FirebaseAuth firebaseAuth, GoogleSignIn googleSignIn}) 12 | : _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance, 13 | _googleSignIn = googleSignIn ?? GoogleSignIn(); 14 | final FirebaseAuth _firebaseAuth; 15 | final GoogleSignIn _googleSignIn; 16 | 17 | final FirestoreRepository firestoreRepository = FirestoreRepository(); 18 | 19 | Future signInWithGoogle() async { 20 | final GoogleSignInAccount googleUser = await _googleSignIn.signIn(); 21 | final GoogleSignInAuthentication googleAuth = 22 | await googleUser.authentication; 23 | final AuthCredential credential = GoogleAuthProvider.getCredential( 24 | idToken: googleAuth.idToken, accessToken: googleAuth.accessToken); 25 | await _firebaseAuth.signInWithCredential(credential); 26 | return _firebaseAuth.currentUser(); 27 | } 28 | 29 | Future signInWithCredentials(String email, String password) { 30 | // final AuthResult res = await _firebaseAuth.signInWithEmailAndPassword( 31 | // email: email, password: password); 32 | // final FirebaseUser user = res.user; 33 | // return 34 | return _firebaseAuth.signInWithEmailAndPassword( 35 | email: email, password: password); 36 | } 37 | 38 | Future signUp(String email, String password) async { 39 | firestoreRepository.createNewUser(email); 40 | return await _firebaseAuth.createUserWithEmailAndPassword( 41 | email: email, password: password); 42 | } 43 | 44 | Future signOut() async { 45 | return Future.wait( 46 | [_firebaseAuth.signOut(), _googleSignIn.signOut()]); 47 | } 48 | 49 | Future isSignedIn() async { 50 | final currentUser = _firebaseAuth.currentUser(); 51 | return currentUser != null; 52 | } 53 | 54 | // use this to get the UID instead 55 | Future getUser() async { 56 | return (await _firebaseAuth.currentUser()).uid; 57 | } 58 | 59 | Future getUserEmail() async { 60 | print('lol'); 61 | return (await _firebaseAuth.currentUser()).email; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 12 | 19 | 23 | 27 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /lib/features/auth/presentation/bloc/login_bloc/login_bloc_state.dart: -------------------------------------------------------------------------------- 1 | part of 'login_bloc.dart'; 2 | 3 | class LoginState { 4 | final bool isEmailValid; 5 | final bool isPasswordValid; 6 | final bool isSubmitting; 7 | final bool isSuccess; 8 | final bool isFailure; 9 | 10 | LoginState( 11 | {this.isEmailValid, 12 | this.isPasswordValid, 13 | this.isSubmitting, 14 | this.isSuccess, 15 | this.isFailure}); 16 | bool get isFormValid => isEmailValid && isPasswordValid; 17 | 18 | factory LoginState.empty() { 19 | return LoginState( 20 | isEmailValid: true, 21 | isPasswordValid: true, 22 | isSubmitting: false, 23 | isSuccess: false, 24 | isFailure: false, 25 | ); 26 | } 27 | 28 | factory LoginState.loading() { 29 | return LoginState( 30 | isEmailValid: true, 31 | isPasswordValid: true, 32 | isSubmitting: true, 33 | isSuccess: false, 34 | isFailure: false, 35 | ); 36 | } 37 | 38 | factory LoginState.failure() { 39 | return LoginState( 40 | isEmailValid: true, 41 | isPasswordValid: true, 42 | isSubmitting: false, 43 | isSuccess: false, 44 | isFailure: true, 45 | ); 46 | } 47 | 48 | factory LoginState.success() { 49 | return LoginState( 50 | isEmailValid: true, 51 | isPasswordValid: true, 52 | isSubmitting: false, 53 | isSuccess: true, 54 | isFailure: false, 55 | ); 56 | } 57 | 58 | LoginState update({ 59 | bool isEmailValid, 60 | bool isPasswordValid, 61 | }) { 62 | return copyWith( 63 | isEmailValid: isEmailValid, 64 | isPasswordValid: isPasswordValid, 65 | isSubmitting: false, 66 | isSuccess: false, 67 | isFailure: false, 68 | ); 69 | } 70 | 71 | LoginState copyWith({ 72 | bool isEmailValid, 73 | bool isPasswordValid, 74 | bool isSubmitEnabled, 75 | bool isSubmitting, 76 | bool isSuccess, 77 | bool isFailure, 78 | }) { 79 | return LoginState( 80 | isEmailValid: isEmailValid ?? this.isEmailValid, 81 | isPasswordValid: isPasswordValid ?? this.isPasswordValid, 82 | isSubmitting: isSubmitting ?? this.isSubmitting, 83 | isSuccess: isSuccess ?? this.isSuccess, 84 | isFailure: isFailure ?? this.isFailure, 85 | ); 86 | } 87 | 88 | @override 89 | String toString() { 90 | return '''LoginState { 91 | isEmailValid: $isEmailValid, 92 | isPasswordValid: $isPasswordValid, 93 | isSubmitting: $isSubmitting, 94 | isSuccess: $isSuccess, 95 | isFailure: $isFailure, 96 | }'''; 97 | } 98 | 99 | @override 100 | List get props => []; 101 | } 102 | -------------------------------------------------------------------------------- /lib/features/auth/presentation/bloc/register_bloc/register_state.dart: -------------------------------------------------------------------------------- 1 | part of 'register_bloc.dart'; 2 | 3 | 4 | 5 | @immutable 6 | class RegisterState { 7 | final bool isEmailValid; 8 | final bool isPasswordValid; 9 | final bool isSubmitting; 10 | final bool isSuccess; 11 | final bool isFailure; 12 | 13 | bool get isFormValid => isEmailValid && isPasswordValid; 14 | 15 | RegisterState({ 16 | @required this.isEmailValid, 17 | @required this.isPasswordValid, 18 | @required this.isSubmitting, 19 | @required this.isSuccess, 20 | @required this.isFailure, 21 | }); 22 | 23 | factory RegisterState.empty() { 24 | return RegisterState( 25 | isEmailValid: true, 26 | isPasswordValid: true, 27 | isSubmitting: false, 28 | isSuccess: false, 29 | isFailure: false, 30 | ); 31 | } 32 | 33 | factory RegisterState.loading() { 34 | return RegisterState( 35 | isEmailValid: true, 36 | isPasswordValid: true, 37 | isSubmitting: true, 38 | isSuccess: false, 39 | isFailure: false, 40 | ); 41 | } 42 | 43 | factory RegisterState.failure() { 44 | return RegisterState( 45 | isEmailValid: true, 46 | isPasswordValid: true, 47 | isSubmitting: false, 48 | isSuccess: false, 49 | isFailure: true, 50 | ); 51 | } 52 | 53 | factory RegisterState.success() { 54 | return RegisterState( 55 | isEmailValid: true, 56 | isPasswordValid: true, 57 | isSubmitting: false, 58 | isSuccess: true, 59 | isFailure: false, 60 | ); 61 | } 62 | 63 | RegisterState update({ 64 | bool isEmailValid, 65 | bool isPasswordValid, 66 | }) { 67 | return copyWith( 68 | isEmailValid: isEmailValid, 69 | isPasswordValid: isPasswordValid, 70 | isSubmitting: false, 71 | isSuccess: false, 72 | isFailure: false, 73 | ); 74 | } 75 | 76 | RegisterState copyWith({ 77 | bool isEmailValid, 78 | bool isPasswordValid, 79 | bool isSubmitEnabled, 80 | bool isSubmitting, 81 | bool isSuccess, 82 | bool isFailure, 83 | }) { 84 | return RegisterState( 85 | isEmailValid: isEmailValid ?? this.isEmailValid, 86 | isPasswordValid: isPasswordValid ?? this.isPasswordValid, 87 | isSubmitting: isSubmitting ?? this.isSubmitting, 88 | isSuccess: isSuccess ?? this.isSuccess, 89 | isFailure: isFailure ?? this.isFailure, 90 | ); 91 | } 92 | 93 | @override 94 | String toString() { 95 | return '''RegisterState { 96 | isEmailValid: $isEmailValid, 97 | isPasswordValid: $isPasswordValid, 98 | isSubmitting: $isSubmitting, 99 | isSuccess: $isSuccess, 100 | isFailure: $isFailure, 101 | }'''; 102 | } 103 | } -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:e_vote/features/auth/data/user_repository.dart'; 2 | import 'package:e_vote/features/auth/presentation/bloc/auth_bloc/auth_bloc.dart'; 3 | import 'package:e_vote/features/auth/presentation/bloc/auth_bloc/auth_events.dart'; 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_bloc/flutter_bloc.dart'; 7 | import 'package:google_fonts/google_fonts.dart'; 8 | import 'package:provider/provider.dart'; 9 | import 'features/auth/presentation/bloc/auth_bloc/auth_states.dart'; 10 | import 'features/auth/presentation/screens/Login_Screen.dart'; 11 | import 'home_screen.dart'; 12 | import 'features/auth/presentation/screens/splash_screen.dart'; 13 | 14 | void main() { 15 | runApp(HomePage()); 16 | } 17 | 18 | class HomePage extends StatefulWidget { 19 | @override 20 | _HomePageState createState() => _HomePageState(); 21 | } 22 | 23 | class _HomePageState extends State { 24 | final UserRepository _userRepository = UserRepository(); 25 | AuthBloc _authBloc; 26 | //An instance of user_Repository and AuthBloc is created 27 | @override 28 | void initState() { 29 | super.initState(); 30 | _authBloc = AuthBloc(repository: _userRepository); 31 | _authBloc.add(AppStarted()); 32 | } 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | // ignore: always_specify_types 37 | return BlocProvider( 38 | create: (BuildContext context) => _authBloc, 39 | child: MaterialApp( 40 | debugShowCheckedModeBanner: false, 41 | theme: ThemeData.from( 42 | textTheme: GoogleFonts.montserratTextTheme( 43 | Theme.of(context).textTheme 44 | ), 45 | colorScheme: ColorScheme( 46 | primary: Color(0xFFf4511e), 47 | primaryVariant: Color(0xffb91400), 48 | secondary: Color(0xFF616161), 49 | secondaryVariant: Color(0xFF373737), 50 | surface: Color(0xFFF2F2F2), 51 | background: Color(0xFFF2F2F2), 52 | error: Color(0xffad1457), 53 | onPrimary: Colors.black54, 54 | onSecondary: Colors.black87, 55 | onSurface: Colors.black54, 56 | onBackground: Colors.black87, 57 | onError: Colors.black87, 58 | brightness: Brightness.light)), 59 | home: BlocBuilder( 60 | bloc: _authBloc, 61 | builder: (BuildContext context, AuthState state) { 62 | if (state is AppStarted) return SplashScreen(); 63 | if (state is Authenticated) 64 | return HomeScreen( 65 | userRepository: _userRepository, 66 | ); 67 | if (state is Unauthenticated) 68 | return LoginScreen(userRepository: _userRepository); 69 | return Container(); 70 | }), 71 | ), 72 | ); 73 | } 74 | 75 | @override 76 | void dispose() { 77 | _authBloc.close(); 78 | super.dispose(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /lib/features/ui/bloc/admin_bloc/admin_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:bloc/bloc.dart'; 3 | import 'package:e_vote/backend/errors.dart'; 4 | import 'package:e_vote/backend/remote_datasource.dart'; 5 | import 'package:e_vote/models/candidate_model.dart'; 6 | import 'package:e_vote/models/voter_model.dart'; 7 | import 'package:equatable/equatable.dart'; 8 | import 'package:flutter/material.dart'; 9 | part 'admin_event.dart'; 10 | part 'admin_state.dart'; 11 | 12 | class AdminBloc extends Bloc { 13 | AdminState get initialState => AdminInitial(); 14 | final ElectionDataSource dataSource = ElectionDataSource(); 15 | @override 16 | Stream mapEventToState( 17 | AdminEvent event, 18 | ) async* { 19 | if (event is DisplayCandidates) { 20 | List candidates = await dataSource.getAllCandidates(); 21 | yield CandidatesList(candidates: candidates); 22 | } else if (event is DisplayVoters) { 23 | List voters = await dataSource.getAllVoters(); 24 | yield VotersList(voters: voters); 25 | } else if (event is GetElectionDetails) { 26 | final String description = await dataSource.getDescription(); 27 | final String adminAddress = await dataSource.adminAddress; 28 | final String electionsState = await dataSource.getElectionState(); 29 | print(electionsState); 30 | yield ElectionDetailsState( 31 | adminAddress: adminAddress, 32 | description: description, 33 | electionState: electionsState); 34 | } else if (event is StartElection) { 35 | final result = await dataSource.startElection(); 36 | yield* result.fold((error) async* { 37 | yield Loading(); 38 | yield AdminError(errorMessage: error); 39 | }, (txHash) async* { 40 | yield Loading(); 41 | yield ElectionTxHash(txHash: txHash); 42 | }); 43 | } else if (event is EndElection) { 44 | final result = await dataSource.endElection(); 45 | yield* result.fold((error) async* { 46 | yield Loading(); 47 | yield AdminError(errorMessage: error); 48 | }, (txHash) async* { 49 | yield Loading(); 50 | yield ElectionTxHash(txHash: txHash); 51 | }); 52 | } else if (event is AddCandidate) { 53 | final result = await dataSource.addCandidate(event.name, event.proposal); 54 | yield* result.fold((error) async* { 55 | yield Loading(); 56 | yield AdminError(errorMessage: error); 57 | }, (txHash) async* { 58 | yield Loading(); 59 | yield ElectionTxHash(txHash: txHash); 60 | }); 61 | } else if (event is AddVoter) { 62 | final result = await dataSource.addVoter(event.voterAddress); 63 | yield* result.fold((error) async* { 64 | yield Loading(); 65 | yield AdminError(errorMessage: error); 66 | }, (txHash) async* { 67 | yield Loading(); 68 | yield ElectionTxHash(txHash: txHash); 69 | }); 70 | } else if (event is ShowResults) { 71 | final results = await dataSource.showResults(); 72 | yield* results.fold((e) async* { 73 | yield AdminError(errorMessage: e); 74 | }, (results) async* { 75 | results.sort((a, b) => b.count.compareTo(a.count)); 76 | yield Results(results: results, winner: results[0]); 77 | }); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/features/ui/bloc/voter_bloc/voter_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:bloc/bloc.dart'; 3 | import 'package:e_vote/backend/errors.dart'; 4 | import 'package:e_vote/backend/remote_datasource.dart'; 5 | import 'package:e_vote/database/firestore_repository.dart'; 6 | import 'package:e_vote/features/auth/data/user_repository.dart'; 7 | import 'package:e_vote/features/ui/bloc/admin_bloc/admin_bloc.dart'; 8 | import 'package:e_vote/features/ui/bloc/user_bloc/user_bloc_bloc.dart'; 9 | import 'package:e_vote/models/candidate_model.dart'; 10 | import 'package:e_vote/models/voter_model.dart'; 11 | import 'package:equatable/equatable.dart'; 12 | import 'package:flutter/material.dart'; 13 | 14 | part 'voter_event.dart'; 15 | part 'voter_state.dart'; 16 | 17 | class VoterBloc extends Bloc { 18 | VoterState get initialState => VoterInitial(); 19 | final ElectionDataSource dataSource = ElectionDataSource(); 20 | final FirestoreRepository repo = FirestoreRepository(); 21 | final UserRepository userRepository = UserRepository(); 22 | @override 23 | Stream mapEventToState( 24 | VoterEvent event, 25 | ) async* { 26 | if (event is ShowElectionDetails) { 27 | final String description = await dataSource.getDescription(); 28 | final String adminAddress = await dataSource.adminAddress; 29 | final String electionsState = await dataSource.getElectionState(); 30 | print(electionsState); 31 | yield ElectionDetailsState( 32 | adminAddress: adminAddress, 33 | description: description, 34 | electionState: electionsState); 35 | } else if (event is DisplayCandidates) { 36 | List candidates = await dataSource.getAllCandidates(); 37 | yield CandidatesList(candidates: candidates); 38 | } else if (event is GetVoterProfile) { 39 | String email = await userRepository.getUserEmail(); 40 | String address = await repo.getVoterAddress(email); 41 | Voter voterProfile = await dataSource.getVoterProfile(address); 42 | print(voterProfile.delegateAddress); 43 | yield VoterProfileState(voterProfile: voterProfile, address: address); 44 | } else if (event is ShowResults) { 45 | final results = await dataSource.showResults(); 46 | yield* results.fold((e) async* { 47 | print('Error'); 48 | yield VoterError(errorMessage: e); 49 | }, (results) async* { 50 | results.sort((a, b) => b.count.compareTo(a.count)); 51 | yield Results(results: results, winner: results[0]); 52 | }); 53 | } else if (event is DelegateVote) { 54 | String email = await userRepository.getUserEmail(); 55 | String ownerAddress = await repo.getVoterAddress(email); 56 | final results = 57 | await dataSource.delegateVoter(event.delegateAddress, ownerAddress); 58 | yield* results.fold((e) async* { 59 | yield Loading(); 60 | yield VoterError(errorMessage: e); 61 | }, (response) async* { 62 | yield Loading(); 63 | yield ElectionTxHash(txHash: response); 64 | }); 65 | } else if (event is Vote) { 66 | String email = await userRepository.getUserEmail(); 67 | String ownerAddress = await repo.getVoterAddress(email); 68 | final result = await dataSource.vote(event.candidateID, ownerAddress); 69 | yield* result.fold((e) async* { 70 | yield Loading(); 71 | yield VoterError(errorMessage: e); 72 | }, (response) async* { 73 | yield Loading(); 74 | yield ElectionTxHash(txHash: response); 75 | }); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: e_vote 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.7.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | web3dart: ^1.2.3 27 | random_color_scheme: ^0.0.1 28 | web_socket_channel: ^1.1.0 29 | http: ^0.12.1 30 | provider: ^4.1.3 31 | cloud_firestore: ^0.13.7 32 | firebase_auth: ^0.16.1 33 | google_fonts: ^1.1.0 34 | # The following adds the Cupertino Icons font to your application. 35 | # Use with the CupertinoIcons class for iOS style icons. 36 | cupertino_icons: ^0.1.3 37 | equatable: ^1.2.3 38 | flutter_bloc: ^4.0.0 39 | flutter_icons: ^1.1.0 40 | google_sign_in: ^4.5.1 41 | dio: ^3.0.10 42 | crypto: ^2.1.4 43 | dartz: ^0.9.2 44 | pull_to_refresh: ^1.6.2 45 | dev_dependencies: 46 | flutter_launcher_icons: ^0.8.0 47 | flutter_test: 48 | sdk: flutter 49 | flutter_icons: 50 | android: true 51 | ios: true 52 | image_path_ios: "assets/ballot-box.png" 53 | image_path_android: "assets/ballot-box.png" 54 | adaptive_icon_background: "assets/ballot-box.png" 55 | adaptive_icon_foreground: "assets/ballot-box.png" 56 | 57 | 58 | # For information on the generic Dart part of this file, see the 59 | # following page: https://dart.dev/tools/pub/pubspec 60 | 61 | # The following section is specific to Flutter. 62 | flutter: 63 | 64 | # The following line ensures that the Material Icons font is 65 | # included with your application, so that you can use the icons in 66 | # the material Icons class. 67 | uses-material-design: true 68 | assets: 69 | - assets/ballot-box.png 70 | - assets/voter.png 71 | - assets/colored-ballot-box.png 72 | - assets/winning.png 73 | - assets/404-error.png 74 | - assets/trophy.png 75 | # To add assets to your application, add an assets section, like this: 76 | # assets: 77 | # - images/a_dot_burr.jpeg 78 | # - images/a_dot_ham.jpeg 79 | 80 | # An image asset can refer to one or more resolution-specific "variants", see 81 | # https://flutter.dev/assets-and-images/#resolution-aware. 82 | 83 | # For details regarding adding assets from package dependencies, see 84 | # https://flutter.dev/assets-and-images/#from-packages 85 | 86 | # To add custom fonts to your application, add a fonts section here, 87 | # in this "flutter" section. Each entry in this list should have a 88 | # "family" key with the font family name, and a "fonts" key with a 89 | # list giving the asset and other descriptors for the font. For 90 | # example: 91 | # fonts: 92 | # - family: Schyler 93 | # fonts: 94 | # - asset: fonts/Schyler-Regular.ttf 95 | # - asset: fonts/Schyler-Italic.ttf 96 | # style: italic 97 | # - family: Trajan Pro 98 | # fonts: 99 | # - asset: fonts/TrajanPro.ttf 100 | # - asset: fonts/TrajanPro_Bold.ttf 101 | # weight: 700 102 | # 103 | # For details regarding fonts from package dependencies, 104 | # see https://flutter.dev/custom-fonts/#from-packages 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # E-Vote 2 | 3 | A decentralized and transparent Blockchain-based voting application, built with Flutter as the UI framework, a Solidity Smart contract as the Backend and MaticVigil Sidechain as the Database (Blockchain). 4 | 5 | ## E-Vote Architecture Diagram 6 | ![System Architecture](EVote_Architecture_Diagram.png) 7 | 8 | 9 | ## Issues with the Current System of Voting 10 | 1. Voting in most democratic states and organizations all over the world has been a largely centralized activity, with immense trust and faith placed in the hands of a small number of powerful people. 11 | 2. Absence of a robust and secure authentication system for voters (like a biometric system). 12 | 3. Security issues with respect to the manipulation of EVMs - Rigging in elections is a major threat to democracy. 13 | 4. Another factor that negatively impacts the current system of Voting is the associated costs and other resources. For example, it was estimated that the Indian Lok Sabha Elections of 2019 cost $7 billion. 14 | 5. Both Digital Voting using EVMs and the Paper Ballot system have drawbacks - reliability, verifiability and security. 15 | 16 | ## What is Liquid Democracy? 17 | 18 | Liquid democracy is similar to ‘delegative democracy’ which combines elements of both direct and representative democracy. 19 | 20 | It is essentially a scalable hybrid of direct and representative democracy, each of which has its disadvantages - direct democracy is not scalable and representative democracy is not reflective of democratic ideals, which is to put power in the hands of the people. 21 | 22 | It enables people to vote directly, or assign their vote to individuals or organizations that they trust. 23 | 24 | It is being proposed in order to strengthen democracy by increasing popular participation in everyday policy making. 25 | 26 | ## Running the Flutter App 27 | 1. Clone the repo. 28 | 2. Download the necessary packages required by Flutter by running `flutter pub get`. 29 | 3. Run the application on an emulator (on Android Studio) or use a physical device (Enable USB Debugging on the device) by running the command `flutter run`. 30 | 31 | ## Overview of Matic and reasons for its use 32 | 33 | 1. The current decentralized eco-system lacks usability and convenience and transactions are slow, expensive and complex. Most blockchain applications haven’t reached a stage of mass usage due to scalability and UI issues. 34 | 2. Matic Network is a Layer 2 scaling solution that achieves scale by utilizing side chains for off-chain computation while ensuring asset security using the Plasma framework and a DPoS validators. 35 | 3. MaticVigil API is an API gateway on top of the Matic network EVM-compatible sidechain which enables developers to write code to interact with value transfer and transactions on Matic as if it were just talking to a normal web server REST API over HTTP. 36 | 4. Due to the primitive stages of web3 libraries in Dart, I have chosen to use Matic to deploy and communicate with the smart contract. 37 | 38 | ![MaticVigil](matic.png) 39 | 40 | ###### Source:https://medium.com/blockvigil/maticvigil-scalable-blockchain-api-for-everyone-2bc09489e602 41 | 42 | ## Deploying a new Smart contract to the Matic Network and running the app successfully 43 | 44 | 1. Start by configuring the app’s Firebase integration to your Google account - Create a new project in Firebase (https://console.firebase.google.com/u/0/ ), and integrate it with the app, enable Email-Password Authentication and Firestore Read/Write access. 45 | 2. Register as a user from an account - which will be the Admin of the election. 46 | 3.Create a collection called ‘users’ in the root folder - and add a new document, adding in three fields - 47 | - email - Set it to the email used in the previous step. 48 | - admin - Set this field to true, it indicates whether the particular user is the admin or not 49 | - address - This is the 40-character long (42, including the leading ‘0x’) that will act as the Public Ethereum Address of the Administrator of the election. This can be generated randomly. 50 | - Visit the MaticVigil Mainnet (An Ethereum Sidechain Solution that lets you deploy a smart contract to its network and creates an API for you) website (https://mainnet.maticvigil.com/ ), create a new developer account and click on “Add a New Contract” 51 | 5. Copy the code present in contracts/Election.sol to the online IDE and Deploy it - Enter the appropriate description and and enter the admin address entered in the previous steps as the other argument and deploy the contract. 52 | 6. After the contract has been deployed to the Matic Network, copy the ‘Contract Address’ present on the contract dashboard and replace the existing contract address and admin address in backend/remote_datasource.dart as url and adminAddress respectively. 53 | 7. Run the Flutter app 54 | 55 | ## Screenshots of Working 56 |   57 |   58 |   59 |   60 |   61 |   62 |   63 |   64 |   65 |   66 |   67 |   68 |   69 |   70 |   71 |   72 |   73 |   74 |   75 |   76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /lib/features/auth/presentation/screens/register_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:e_vote/features/auth/presentation/bloc/auth_bloc/auth_bloc.dart'; 2 | import 'package:e_vote/features/auth/presentation/bloc/auth_bloc/auth_events.dart'; 3 | import 'package:e_vote/features/auth/presentation/bloc/register_bloc/register_bloc.dart'; 4 | import 'package:e_vote/features/auth/presentation/screens/Login_Screen.dart'; 5 | import 'package:e_vote/features/auth/presentation/widgets/Register_button.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_bloc/flutter_bloc.dart'; 8 | 9 | //read login_form.dart for documentation 10 | class RegisterForm extends StatefulWidget { 11 | State createState() => _RegisterFormState(); 12 | } 13 | 14 | class _RegisterFormState extends State { 15 | final TextEditingController _emailController = TextEditingController(); 16 | final TextEditingController _passwordController = TextEditingController(); 17 | 18 | RegisterBloc _registerBloc; 19 | 20 | bool get isPopulated => 21 | _emailController.text.isNotEmpty && _passwordController.text.isNotEmpty; 22 | 23 | /* Checks whether the form is valid and populated and is not in the submission phase - only then 24 | enables the Register Button 25 | */ 26 | bool isRegisterButtonEnabled(RegisterState state) { 27 | return state.isFormValid && isPopulated && !state.isSubmitting; 28 | } 29 | 30 | @override 31 | void initState() { 32 | super.initState(); 33 | _registerBloc = BlocProvider.of(context); 34 | _emailController.addListener(_onEmailChanged); 35 | _passwordController.addListener(_onPasswordChanged); 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return BlocListener( 41 | bloc: _registerBloc, 42 | listener: (BuildContext context, RegisterState state) { 43 | if (state.isSubmitting) { 44 | Scaffold.of(context) 45 | ..hideCurrentSnackBar() 46 | ..showSnackBar( 47 | SnackBar( 48 | content: Row( 49 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 50 | children: [ 51 | Text('Registering...'), 52 | CircularProgressIndicator(), 53 | ], 54 | ), 55 | ), 56 | ); 57 | } 58 | if (state.isSuccess) { 59 | BlocProvider.of(context).add(LoggedIn()); 60 | Navigator.of(context).pop(); 61 | } 62 | if (state.isFailure) { 63 | Scaffold.of(context) 64 | ..hideCurrentSnackBar() 65 | ..showSnackBar( 66 | SnackBar( 67 | content: Row( 68 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 69 | children: [ 70 | Text('Registration Failure'), 71 | Icon(Icons.error), 72 | ], 73 | ), 74 | backgroundColor: Colors.red, 75 | ), 76 | ); 77 | } 78 | }, 79 | child: BlocBuilder( 80 | bloc: _registerBloc, 81 | builder: (BuildContext context, RegisterState state) { 82 | return Padding( 83 | padding: EdgeInsets.all(20), 84 | child: Form( 85 | child: ListView( 86 | children: [ 87 | SizedBox( 88 | height: 20, 89 | ), 90 | Align( 91 | alignment: Alignment.centerLeft, 92 | child: IconButton( 93 | color: Color(0xFFf4511e), 94 | icon: Icon(Icons.arrow_back), 95 | onPressed: () { 96 | Navigator.of(context).pop(); 97 | }), 98 | ), 99 | SizedBox(height: 80), 100 | Container( 101 | height: 100, 102 | width: 100, 103 | child: Image.asset("assets/colored-ballot-box.png", 104 | fit: BoxFit.contain)), 105 | TextFormField( 106 | controller: _emailController, 107 | decoration: InputDecoration( 108 | icon: Icon(Icons.email), 109 | labelText: 'Email', 110 | ), 111 | autocorrect: false, 112 | autovalidate: true, 113 | validator: (_) { 114 | return !state.isEmailValid ? 'Invalid Email' : null; 115 | }, 116 | ), 117 | TextFormField( 118 | controller: _passwordController, 119 | decoration: InputDecoration( 120 | errorMaxLines: 3, 121 | icon: Icon(Icons.lock), 122 | labelText: 'Password', 123 | ), 124 | obscureText: true, 125 | autocorrect: false, 126 | autovalidate: true, 127 | validator: (_) { 128 | return !state.isPasswordValid 129 | ? 'Invalid Password. The password must contain atleast 8 characters, one lowercase alphabet and one digit.' 130 | : null; 131 | }, 132 | ), 133 | SizedBox( 134 | height: 20, 135 | ), 136 | RegisterButton( 137 | onPressed: isRegisterButtonEnabled(state) 138 | ? _onFormSubmitted 139 | : null, 140 | ), 141 | ], 142 | ), 143 | ), 144 | ); 145 | }, 146 | ), 147 | ); 148 | } 149 | 150 | @override 151 | void dispose() { 152 | _emailController.dispose(); 153 | _passwordController.dispose(); 154 | super.dispose(); 155 | } 156 | 157 | void _onEmailChanged() { 158 | _registerBloc.add( 159 | EmailChanged(email: _emailController.text), 160 | ); 161 | } 162 | 163 | void _onPasswordChanged() { 164 | _registerBloc.add( 165 | PasswordChanged(password: _passwordController.text), 166 | ); 167 | } 168 | 169 | void _onFormSubmitted() { 170 | _registerBloc.add( 171 | Submitted( 172 | email: _emailController.text, 173 | password: _passwordController.text, 174 | ), 175 | ); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /lib/features/auth/presentation/screens/Login_Form.dart: -------------------------------------------------------------------------------- 1 | import 'package:e_vote/features/auth/data/user_repository.dart'; 2 | import 'package:e_vote/features/auth/presentation/bloc/auth_bloc/auth_bloc.dart'; 3 | import 'package:e_vote/features/auth/presentation/bloc/auth_bloc/auth_events.dart'; 4 | import 'package:e_vote/features/auth/presentation/bloc/login_bloc/login_bloc.dart'; 5 | import 'package:e_vote/features/auth/presentation/widgets/create_account_button.dart'; 6 | import 'package:e_vote/features/auth/presentation/widgets/login_button.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_bloc/flutter_bloc.dart'; 9 | 10 | //the LoginForm widget is made Stateful so as to handle the TextEditingControllers' state. 11 | class LoginForm extends StatefulWidget { 12 | // This instance UserRepository is received/injected from the LoginScreen widget. 13 | final UserRepository _userRepository; 14 | 15 | //The key is used to uniquely identify the form widget. 16 | LoginForm({Key key, @required UserRepository userRepository}) 17 | : assert(userRepository != null), 18 | _userRepository = userRepository, 19 | super(key: key); 20 | @override 21 | _LoginFormState createState() => _LoginFormState(); 22 | } 23 | 24 | class _LoginFormState extends State { 25 | final TextEditingController _emailController = TextEditingController(); 26 | final TextEditingController _passwordController = TextEditingController(); 27 | 28 | // An instance of LoginBloc has been provided by the parent widget (LoginScreen) 29 | LoginBloc _loginBloc; 30 | 31 | UserRepository get _userRepository => widget._userRepository; 32 | 33 | //Just checks if password and email are empty 34 | bool get isPopulated => 35 | _emailController.text.isNotEmpty && _passwordController.text.isNotEmpty; 36 | 37 | //Checks if password and email are valid, and the state is not under submission 38 | bool isLoginButtonEnabled(LoginState state) { 39 | return state.isFormValid && !state.isSubmitting && isPopulated; 40 | } 41 | 42 | @override 43 | void initState() { 44 | _loginBloc = BlocProvider.of( 45 | context); //The parent class' bloc is used here. 46 | _emailController.addListener(_onEmailChanged); 47 | _passwordController.addListener(_onPasswordChanged); 48 | super.initState(); 49 | } 50 | 51 | @override 52 | Widget build(BuildContext context) { 53 | return BlocListener( 54 | bloc: _loginBloc, 55 | listener: (BuildContext context, LoginState state) { 56 | //state.isFailure => email, password and failure are True, but submitting and success are false 57 | if (state.isFailure) 58 | Scaffold.of(context) 59 | ..hideCurrentSnackBar() 60 | ..showSnackBar( 61 | SnackBar( 62 | content: Row( 63 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 64 | children: [const Text('Login Failure'), Icon(Icons.error)], 65 | ), 66 | backgroundColor: Colors.red, 67 | ), 68 | ); 69 | 70 | //state.isSubmitting => email, password and isSubmitting is true, whereas success and failure are false 71 | if (state.isSubmitting) 72 | Scaffold.of(context) 73 | ..hideCurrentSnackBar() 74 | ..showSnackBar( 75 | SnackBar( 76 | content: Row( 77 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 78 | children: [ 79 | Text('Logging In...'), 80 | CircularProgressIndicator(), 81 | ], 82 | ), 83 | ), 84 | ); 85 | // state.isSuccess => email, password and success are true - LoggedIn() event is dispatched to the AuthBloc 86 | if (state.isSuccess) BlocProvider.of(context).add(LoggedIn()); 87 | }, 88 | child: BlocBuilder( 89 | bloc: _loginBloc, 90 | builder: (BuildContext context, LoginState state) { 91 | return Padding( 92 | padding: EdgeInsets.all(20), 93 | child: Form( 94 | child: ListView( 95 | children: [ 96 | Padding( 97 | padding: EdgeInsets.symmetric(vertical: 20), 98 | child: Container(height: 110), 99 | ), 100 | Container( 101 | height: 100, 102 | width: 100, 103 | child: Image.asset("assets/colored-ballot-box.png", fit: BoxFit.contain)), 104 | TextFormField( 105 | controller: _emailController, 106 | decoration: InputDecoration( 107 | icon: Icon(Icons.email), labelText: 'Email'), 108 | autovalidate: true, 109 | autocorrect: false, 110 | validator: (_) { 111 | return !state.isEmailValid ? 'Invalid Email' : null; 112 | }, 113 | ), 114 | TextFormField( 115 | controller: _passwordController, 116 | decoration: InputDecoration( 117 | icon: Icon(Icons.lock), labelText: 'Password'), 118 | obscureText: true, 119 | autovalidate: true, 120 | autocorrect: false, 121 | validator: (_) { 122 | return !state.isPasswordValid ? 'Invalid password' : null; 123 | }, 124 | ), 125 | Padding( 126 | padding: const EdgeInsets.symmetric(vertical: 20), 127 | child: Column( 128 | crossAxisAlignment: CrossAxisAlignment.stretch, 129 | children: [ 130 | LoginButton( 131 | onPressed: isLoginButtonEnabled(state) 132 | ? _onFormSubmitted 133 | : null, 134 | ), 135 | CreateAccountButton( 136 | userRepository: _userRepository), 137 | ])) 138 | ], 139 | )), 140 | ); 141 | }), 142 | ); 143 | } 144 | 145 | @override 146 | void dispose() { 147 | _emailController.dispose(); 148 | _passwordController.dispose(); 149 | super.dispose(); 150 | } 151 | 152 | 153 | /* The three functions responsible for dispatching the three different types of events - 154 | EmailChanged, PasswordChanged and LoginWithCredentialsPassed 155 | */ 156 | void _onEmailChanged() { 157 | _loginBloc.add( 158 | EmailChanged(email: _emailController.text), 159 | ); 160 | } 161 | 162 | void _onPasswordChanged() { 163 | _loginBloc.add( 164 | PasswordChanged(password: _passwordController.text), 165 | ); 166 | } 167 | 168 | void _onFormSubmitted() 169 | { 170 | _loginBloc.add( 171 | LoginWithCredentialsPressed( 172 | email: _emailController.text, 173 | password: _passwordController.text) 174 | ); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /lib/backend/remote_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:dartz/dartz.dart'; 3 | import 'package:dio/dio.dart'; 4 | import 'package:e_vote/backend/errors.dart'; 5 | import 'package:e_vote/models/candidate_model.dart'; 6 | import 'package:e_vote/models/voter_model.dart'; 7 | 8 | class ElectionDataSource { 9 | var dioClient = Dio(); 10 | String url = 11 | "https://mainnet-api.maticvigil.com/v1.0/contract/0xdaecff888d2c99a072a711168eeed5504e9e8fbc"; 12 | var httpClient = HttpClient(); 13 | String adminAddress = "0xb3eb5933e5eb4b4872142cf631a3b0c686e15216"; 14 | // fetches the address of the admin from the blockchain 15 | Future getAdmin() async { 16 | var response = await dioClient.get(url + "/admin"); 17 | return response.data["data"][0]["address"]; 18 | } 19 | 20 | //Fetches the count of registered candidates in the election 21 | Future getCandidateCount() async { 22 | var response = await dioClient.get(url + "/candidate_count"); 23 | return response.data["data"][0]["uint256"].toInt(); 24 | } 25 | 26 | //Fetches the count of regsitered voters in the elction 27 | Future getVoterCount() async { 28 | var response = await dioClient.get(url + "/voter_count"); 29 | return response.data["data"][0]["uint256"].toInt(); 30 | } 31 | 32 | //Fetches the current state of the election - CREATED, ONGOING or STOPPED 33 | Future getElectionState() async { 34 | var response = await dioClient.get(url + "/checkState"); 35 | return response.data["data"][0]["state"]; 36 | } 37 | 38 | //Fetches a short description of the election 39 | Future getDescription() async { 40 | var response = await dioClient.get(url + "/description"); 41 | return response.data["data"][0]["string"]; 42 | } 43 | 44 | //Fetches the details of a candidate - ID, Name, Proposal 45 | Future getCandidate(int id) async { 46 | var response = await dioClient.get(url + "/displayCandidate/$id"); 47 | return Candidate.fromJson(response.data["data"][0]); 48 | } 49 | 50 | //Fetches the details of all the registered candidates 51 | Future> getAllCandidates() async { 52 | int count = await getCandidateCount(); 53 | var list = List.generate(count, (index) => index + 1); 54 | List result = []; 55 | await Future.wait(list.map((e) async { 56 | await dioClient.get(url + '/displayCandidate/$e').then((value) { 57 | result.add(Candidate.fromJson(value.data["data"][0])); 58 | }); 59 | })); 60 | return result; 61 | } 62 | 63 | //Fetches the details of a voter - ID, Address, DelegateAddress and Weight 64 | Future getVoter(int id, String owner) async { 65 | var response = await dioClient.get(url + "/getVoter/$id/$adminAddress"); 66 | return Voter.fromJson(response.data["data"][0]); 67 | } 68 | 69 | //Fetches the details of all voters 70 | Future> getAllVoters() async { 71 | int count = await getVoterCount(); 72 | var list = List.generate(count, (index) => index + 1); 73 | List result = []; 74 | await Future.wait(list.map((e) async { 75 | await dioClient.get(url + '/getVoter/$e/$adminAddress').then((value) { 76 | result.add(Voter.fromJson(value.data["data"][0])); 77 | }); 78 | })); 79 | print('hah'); 80 | print(result.length); 81 | return result; 82 | } 83 | 84 | //Fetches the result of the candidate 85 | Future> showCandidateResult(int id) async { 86 | try { 87 | var response = await dioClient.get(url + "/showResults/$id"); 88 | return Right(Candidate.result(response.data["data"][0])); 89 | } catch (e) { 90 | return Left(ErrorMessage( 91 | message: e.response.data["error"]["details"]["message"])); 92 | } 93 | } 94 | 95 | //Fetches the results of all the candidates 96 | Future>> showResults() async { 97 | int count = await getCandidateCount(); 98 | if (await getElectionState() != "CONCLUDED") 99 | return Left(ErrorMessage(message: "The election has not concluded yet.")); 100 | var list = List.generate(count, (index) => index + 1); 101 | List result = []; 102 | await Future.wait(list.map((e) async { 103 | await dioClient.get(url + '/showResults/$e').then((value) { 104 | result.add(Candidate.result(value.data["data"][0])); 105 | }); 106 | })); 107 | print(result.length); 108 | return Right(result); 109 | } 110 | 111 | //Returns the winner of the election 112 | Future> getWinner() async { 113 | try { 114 | var response = await dioClient.get(url + "/showWinner"); 115 | return Right(Candidate.winner(response.data["data"][0])); 116 | } catch (e) { 117 | return Left(ErrorMessage( 118 | message: e.response.data["error"]["details"]["message"])); 119 | } 120 | } 121 | 122 | //Function to register a new candidate 123 | Future> addCandidate( 124 | String name, String proposal) async { 125 | Map map = { 126 | "_name": name, 127 | "_proposal": proposal, 128 | "owner": adminAddress 129 | }; 130 | try { 131 | var response = await dioClient.post(url + "/addCandidate", 132 | data: map, 133 | options: Options(headers: { 134 | "X-API-KEY": ["70d56934-be68-4b74-b402-f597cdbd41d9"] 135 | }, contentType: Headers.formUrlEncodedContentType)); 136 | return Right(response.data["data"][0]["txHash"]); 137 | } catch (e) { 138 | return Left(ErrorMessage( 139 | message: e.respone.data["error"]["message"])); 140 | } 141 | } 142 | 143 | //Function to register a new voter 144 | Future> addVoter(String voter) async { 145 | Map map = {"_voter": voter, "owner": adminAddress}; 146 | try { 147 | var response = await dioClient.post(url + "/addVoter", 148 | data: map, 149 | options: Options(headers: { 150 | "X-API-KEY": ["70d56934-be68-4b74-b402-f597cdbd41d9"] 151 | }, contentType: Headers.formUrlEncodedContentType)); 152 | return Right(response.data["data"][0]["txHash"]); 153 | } catch (e) { 154 | if (e.response.data["error"]["message"] == "DataEncodingError") 155 | return Left( 156 | ErrorMessage(message: "Invalid arguments. Please try again.")); 157 | else if (voter == adminAddress) 158 | return Left(ErrorMessage(message: e.response.data["error"]["message"])); 159 | else 160 | return Left(ErrorMessage( 161 | message: e.response.data["error"]["message"])); 162 | } 163 | } 164 | 165 | //Function to delegate your vote to someone else 166 | Future> delegateVoter( 167 | String delegate, String owner) async { 168 | print(delegate + " " + owner); 169 | Map map = {"_delegate": delegate, "owner": owner}; 170 | try { 171 | var response = await dioClient.post(url + "/delegateVote", 172 | data: map, 173 | options: Options(headers: { 174 | "X-API-KEY": ["70d56934-be68-4b74-b402-f597cdbd41d9"] 175 | }, contentType: Headers.formUrlEncodedContentType)); 176 | return Right(response.data["data"][0]["txHash"]); 177 | } catch (e) { 178 | if (e.response.data["error"]["message"] == "DataEncodingError") 179 | return Left( 180 | ErrorMessage(message: "Invalid arguments. Please try again.")); 181 | else 182 | return Left(ErrorMessage(message: e.response.data["error"]["message"])); 183 | } 184 | } 185 | 186 | //Function to endElection 187 | Future> endElection() async { 188 | Map map = {"owner": adminAddress}; 189 | try { 190 | var response = await dioClient.post(url + "/endElection", 191 | data: map, 192 | options: Options(headers: { 193 | "X-API-KEY": ["70d56934-be68-4b74-b402-f597cdbd41d9"] 194 | }, contentType: Headers.formUrlEncodedContentType)); 195 | return Right(response.data["data"][0]["txHash"]); 196 | } catch (e) { 197 | return Left(ErrorMessage(message: e.response.data["error"]["message"])); 198 | } 199 | } 200 | 201 | Future> startElection() async { 202 | Map map = {"owner": adminAddress}; 203 | try { 204 | var response = await dioClient.post(url + "/startElection", 205 | data: map, 206 | options: Options(headers: { 207 | "X-API-KEY": ["70d56934-be68-4b74-b402-f597cdbd41d9"] 208 | }, contentType: Headers.formUrlEncodedContentType)); 209 | return Right(response.data["data"][0]["txHash"]); 210 | } catch (e) { 211 | print(e.response.data["error"]); 212 | return Left(ErrorMessage(message: e.response.data["error"]["message"])); 213 | } 214 | } 215 | 216 | Future> vote(int id, String owner) async { 217 | Map map = {"owner": owner, "_ID": id}; 218 | try { 219 | var response = await dioClient.post(url + "/vote", 220 | data: map, 221 | options: Options(headers: { 222 | "X-API-KEY": ["70d56934-be68-4b74-b402-f597cdbd41d9"] 223 | }, contentType: Headers.formUrlEncodedContentType)); 224 | return Right(response.data["data"][0]["txHash"]); 225 | } catch (e) { 226 | if (e.response.data["error"]["message"] == "DataEncodingError") 227 | return Left( 228 | ErrorMessage(message: "Invalid arguments. Please try again.")); 229 | else 230 | return Left(ErrorMessage(message: e.response.data["error"]["message"])); 231 | } 232 | } 233 | 234 | Future getVoterProfile(String address) async { 235 | var response = await dioClient.get(url + "/voterProfile/$address"); 236 | return Voter.profileJson(response.data["data"][0], address); 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /lib/features/ui/screens/voter_screens/voter_dashboard_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:e_vote/features/auth/presentation/bloc/auth_bloc/auth_bloc.dart'; 2 | import 'package:e_vote/features/auth/presentation/bloc/auth_bloc/auth_events.dart'; 3 | import 'package:e_vote/features/ui/bloc/voter_bloc/voter_bloc.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | import 'package:flutter_icons/flutter_icons.dart'; 7 | 8 | class VoterDashboardPage extends StatefulWidget { 9 | @override 10 | _VoterDashboardPageState createState() => _VoterDashboardPageState(); 11 | } 12 | 13 | class _VoterDashboardPageState extends State { 14 | VoterBloc voterBloc; 15 | 16 | @override 17 | void initState() { 18 | voterBloc = VoterBloc(); 19 | super.initState(); 20 | } 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return BlocProvider( 25 | create: (_) => voterBloc, 26 | child: Container( 27 | child: Scaffold( 28 | appBar: AppBar( 29 | actions: [ 30 | IconButton( 31 | icon: Icon( 32 | Icons.refresh, 33 | color: Colors.black, 34 | ), 35 | onPressed: () { 36 | voterBloc.add(ShowElectionDetails()); 37 | }), 38 | IconButton( 39 | icon: Icon( 40 | MaterialCommunityIcons.logout, 41 | color: Colors.black, 42 | ), 43 | onPressed: () { 44 | BlocProvider.of(context)..add(LoggedOut()); 45 | }), 46 | ], 47 | centerTitle: true, 48 | title: Text( 49 | 'Dashboard', 50 | style: TextStyle(color: Colors.black87), 51 | ), 52 | ), 53 | body: BlocConsumer( 54 | bloc: voterBloc..add(ShowElectionDetails()), 55 | listener: (BuildContext context, VoterState state) { 56 | if (state is VoterError) { 57 | Scaffold.of(context).showSnackBar(SnackBar( 58 | backgroundColor: Colors.red[700], 59 | content: Row( 60 | mainAxisAlignment: MainAxisAlignment.start, 61 | children: [ 62 | SizedBox( 63 | width: 10, 64 | ), 65 | Icon(Icons.error), 66 | SizedBox( 67 | width: 10, 68 | ), 69 | Text( 70 | state.errorMessage.message, 71 | style: TextStyle(color: Colors.white), 72 | ), 73 | ]))); 74 | } else if (state is ElectionTxHash) { 75 | Scaffold.of(context).showSnackBar(SnackBar( 76 | backgroundColor: Colors.green, 77 | content: Row( 78 | mainAxisAlignment: MainAxisAlignment.start, 79 | children: [ 80 | Icon( 81 | Icons.check, 82 | color: Colors.white, 83 | ), 84 | SizedBox( 85 | width: 10, 86 | ), 87 | Flexible( 88 | child: Text( 89 | 'TxHash: ' + state.txHash, 90 | style: TextStyle(color: Colors.white), 91 | textAlign: TextAlign.center, 92 | maxLines: 3, 93 | ), 94 | ), 95 | ]))); 96 | } else if (state is Loading) { 97 | Scaffold.of(context).showSnackBar(SnackBar( 98 | backgroundColor: Theme.of(context).primaryColorDark, 99 | content: Row( 100 | mainAxisAlignment: MainAxisAlignment.start, 101 | children: [ 102 | Container( 103 | height: 35, 104 | child: CircularProgressIndicator( 105 | strokeWidth: 2, 106 | valueColor: new AlwaysStoppedAnimation( 107 | Colors.white), 108 | ), 109 | ), 110 | SizedBox( 111 | width: 10, 112 | ), 113 | Flexible( 114 | child: Text( 115 | 'Loading', 116 | style: TextStyle(color: Colors.white), 117 | textAlign: TextAlign.center, 118 | maxLines: 3, 119 | ), 120 | ), 121 | ]))); 122 | } 123 | }, 124 | buildWhen: (previous, current) { 125 | if (current is VoterError || 126 | current is Loading || 127 | current is ElectionTxHash) { 128 | print('lol'); 129 | return false; 130 | } 131 | return true; 132 | }, 133 | builder: (BuildContext context, VoterState state) { 134 | if (state is ElectionDetailsState) { 135 | return Column( 136 | mainAxisAlignment: MainAxisAlignment.center, 137 | children: [ 138 | Center( 139 | child: Text('ADMIN ADDRESS', 140 | style: TextStyle( 141 | fontSize: 20, 142 | fontWeight: FontWeight.w700, 143 | color: Theme.of(context).primaryColor)), 144 | ), 145 | Padding( 146 | padding: const EdgeInsets.symmetric( 147 | vertical: 10.0, horizontal: 12), 148 | child: Center( 149 | child: Text(state.adminAddress, 150 | textAlign: TextAlign.center, 151 | style: TextStyle( 152 | fontSize: 20, 153 | fontWeight: FontWeight.w700, 154 | color: Theme.of(context).accentColor)), 155 | ), 156 | ), 157 | SizedBox( 158 | height: 20, 159 | ), 160 | Center( 161 | child: Text('DESCRIPTION', 162 | style: TextStyle( 163 | fontSize: 20, 164 | fontWeight: FontWeight.w700, 165 | color: Theme.of(context).primaryColor)), 166 | ), 167 | Padding( 168 | padding: const EdgeInsets.symmetric( 169 | vertical: 10.0, horizontal: 12), 170 | child: Center( 171 | child: Text(state.description, 172 | textAlign: TextAlign.center, 173 | style: TextStyle( 174 | fontSize: 20, 175 | fontWeight: FontWeight.w700, 176 | color: Theme.of(context).accentColor))), 177 | ), 178 | SizedBox( 179 | height: 20, 180 | ), 181 | Center( 182 | child: Text('ELECTION STATE', 183 | style: TextStyle( 184 | fontSize: 20, 185 | fontWeight: FontWeight.w700, 186 | color: Theme.of(context).primaryColor)), 187 | ), 188 | Padding( 189 | padding: const EdgeInsets.symmetric( 190 | vertical: 10.0, horizontal: 12), 191 | child: Center( 192 | child: Text(state.electionState, 193 | textAlign: TextAlign.center, 194 | style: TextStyle( 195 | fontSize: 20, 196 | fontWeight: FontWeight.w700, 197 | color: Theme.of(context).accentColor))), 198 | ), 199 | SizedBox( 200 | height: 20, 201 | ), 202 | ]); 203 | } else { 204 | BlocProvider.of(context) 205 | ..add(ShowElectionDetails()); 206 | return Center( 207 | child: CircularProgressIndicator( 208 | valueColor: new AlwaysStoppedAnimation( 209 | Theme.of(context).primaryColor), 210 | ), 211 | ); 212 | } 213 | }), 214 | ), 215 | ), 216 | ); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /lib/features/ui/screens/voter_screens/delegate_voter.dart: -------------------------------------------------------------------------------- 1 | import 'package:e_vote/features/auth/presentation/bloc/auth_bloc/auth_barrel_bloc.dart'; 2 | import 'package:e_vote/features/ui/bloc/voter_bloc/voter_bloc.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | import 'package:flutter_icons/flutter_icons.dart'; 6 | 7 | class DelegateVoterPage extends StatefulWidget { 8 | @override 9 | _DelegateVoterPageState createState() => _DelegateVoterPageState(); 10 | } 11 | 12 | class _DelegateVoterPageState extends State { 13 | VoterBloc voterBloc; 14 | TextEditingController delegateController; 15 | @override 16 | void initState() { 17 | voterBloc = VoterBloc(); 18 | delegateController = TextEditingController(); 19 | super.initState(); 20 | } 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return BlocProvider( 25 | create: (_) => voterBloc, 26 | child: Container( 27 | child: Scaffold( 28 | appBar: AppBar( 29 | actions: [ 30 | IconButton( 31 | icon: Icon( 32 | Icons.refresh, 33 | color: Colors.black, 34 | ), 35 | onPressed: () { 36 | voterBloc.add(GetVoterProfile()); 37 | }), 38 | IconButton( 39 | icon: Icon( 40 | MaterialCommunityIcons.logout, 41 | color: Colors.black, 42 | ), 43 | onPressed: () { 44 | BlocProvider.of(context)..add(LoggedOut()); 45 | }), 46 | ], 47 | centerTitle: true, 48 | title: Text( 49 | 'Delegate Vote', 50 | style: TextStyle(color: Colors.black87), 51 | ), 52 | ), 53 | body: BlocConsumer( 54 | bloc: voterBloc..add(GetVoterProfile()), 55 | buildWhen: (previous, current) { 56 | if (current is VoterError || 57 | current is Loading || 58 | current is ElectionTxHash) { 59 | return false; 60 | } 61 | return true; 62 | }, 63 | builder: (BuildContext context, VoterState state) { 64 | if (state is VoterProfileState) { 65 | if (BigInt.parse( 66 | state.voterProfile.delegateAddress).toInt() == 67 | 0) { 68 | return Center( 69 | child: Padding( 70 | padding: const EdgeInsets.only( 71 | left: 10.0, top: 12, bottom: 8, right: 20), 72 | child: Column( 73 | mainAxisAlignment: MainAxisAlignment.center, 74 | children: [ 75 | TextFormField( 76 | controller: delegateController, 77 | maxLength: 42, 78 | decoration: InputDecoration( 79 | contentPadding: EdgeInsets.zero, 80 | labelText: "Address of the Delegate", 81 | icon: Icon(Icons.person)), 82 | ), 83 | FlatButton( 84 | color: Theme.of(context).primaryColor, 85 | onPressed: () { 86 | voterBloc.add(DelegateVote( 87 | delegateAddress: 88 | delegateController.text)); 89 | }, 90 | child: Text( 91 | 'DELEGATE', 92 | style: TextStyle( 93 | color: Colors.black, 94 | fontWeight: FontWeight.w600, 95 | fontSize: 20), 96 | )) 97 | ]), 98 | ), 99 | ); 100 | } else { 101 | return Center( 102 | 103 | child: Padding( 104 | padding: const EdgeInsets.all(13.0), 105 | child: Column( 106 | mainAxisAlignment: MainAxisAlignment.center, 107 | children: [ 108 | Flexible( 109 | child: Text( 110 | "You have already delegated your vote to ", 111 | textAlign: TextAlign.center, 112 | style: TextStyle( 113 | color: Theme.of(context).primaryColor, 114 | fontSize: 20, 115 | fontWeight: FontWeight.w700), 116 | ), 117 | ), 118 | SizedBox(height: 17,), 119 | Flexible( 120 | child: Text( 121 | state.voterProfile.delegateAddress, 122 | textAlign: TextAlign.center, 123 | style: TextStyle( 124 | color: Theme.of(context).accentColor, 125 | fontSize: 20, 126 | fontWeight: FontWeight.w700), 127 | ), 128 | ), 129 | ]), 130 | ), 131 | ); 132 | } 133 | } else 134 | return Center( 135 | child: CircularProgressIndicator( 136 | valueColor: new AlwaysStoppedAnimation( 137 | Theme.of(context).primaryColor), 138 | ), 139 | ); 140 | }, 141 | listener: (BuildContext context, VoterState state) { 142 | if (state is VoterError) { 143 | Scaffold.of(context).showSnackBar(SnackBar( 144 | backgroundColor: Colors.red[700], 145 | content: Row( 146 | mainAxisAlignment: MainAxisAlignment.start, 147 | children: [ 148 | SizedBox( 149 | width: 10, 150 | ), 151 | Icon(Icons.error), 152 | SizedBox( 153 | width: 10, 154 | ), 155 | Flexible( 156 | child: Text( 157 | state.errorMessage.message, 158 | style: TextStyle(color: Colors.white), 159 | ), 160 | ), 161 | ]))); 162 | } else if (state is ElectionTxHash) { 163 | Scaffold.of(context).showSnackBar(SnackBar( 164 | backgroundColor: Colors.green, 165 | content: Row( 166 | mainAxisAlignment: MainAxisAlignment.start, 167 | children: [ 168 | Icon( 169 | Icons.check, 170 | color: Colors.white, 171 | ), 172 | SizedBox( 173 | width: 10, 174 | ), 175 | Flexible( 176 | child: Text( 177 | 'TxHash: ' + state.txHash, 178 | style: TextStyle(color: Colors.white), 179 | textAlign: TextAlign.center, 180 | maxLines: 3, 181 | ), 182 | ), 183 | ]))); 184 | } else if (state is Loading) { 185 | Scaffold.of(context).showSnackBar(SnackBar( 186 | backgroundColor: Theme.of(context).primaryColorDark, 187 | content: Row( 188 | mainAxisAlignment: MainAxisAlignment.start, 189 | children: [ 190 | Container( 191 | height: 35, 192 | child: CircularProgressIndicator( 193 | strokeWidth: 2, 194 | valueColor: new AlwaysStoppedAnimation( 195 | Colors.white), 196 | ), 197 | ), 198 | SizedBox( 199 | width: 10, 200 | ), 201 | Flexible( 202 | child: Text( 203 | 'Loading', 204 | style: TextStyle(color: Colors.white), 205 | textAlign: TextAlign.center, 206 | maxLines: 3, 207 | ), 208 | ), 209 | ]))); 210 | } 211 | })), 212 | ), 213 | ); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /contracts/Election.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | pragma solidity ^0.5.0; 3 | 4 | 5 | contract Election { 6 | //Structure that represents a registered voter in the election 7 | struct Voter { 8 | bool isRegistered; 9 | bool hasVoted; //Whether or not the voter has voted yet 10 | address delegate; // The address of the delegate that the voter has chosen (default to the voter itself) 11 | uint256 weight; // Weight of the Voter 12 | uint256 voteTowards; //States the candidate's ID to which the voter has voted 13 | } 14 | //Structure that represents a registered candidate in the election 15 | struct Candidate { 16 | uint256 ID; // The unique ID of the candidate 17 | string name; // Name of the candidate 18 | string proposal; // The proposal/promises of the candidate 19 | } 20 | mapping(uint => address) private voterID; // VoterID mapped to voter address 21 | mapping(address => Voter) private voters; //address of voter mapped to the voter struct - To view all registered voters 22 | mapping(uint256 => Candidate) private candidates; // ID of candidate mapped to the candidate struct - To view details of all registered candidates 23 | mapping(uint256 => uint256) private voteCount; // ID of the candidate mapped to the votes of the candidate privately 24 | 25 | address public admin; // The address of the official/authority conducting the election 26 | 27 | enum State {CREATED, ONGOING, CONCLUDED} 28 | /*This enum represents the state of the election - 29 | CREATED - The election contract has been created, voting has not begun yet 30 | ONGOING - The voting has begun and is currently active 31 | STOP - The voting period has ended and it is time for counting 32 | */ 33 | 34 | State electionState; // A variable of the type enum State to represent the election state 35 | string public description; 36 | uint256 public candidate_count; // Keeps a count of the registered candidates 37 | uint256 public voter_count; 38 | //modifier to check if the address is of the admin's as several functions can only be accessed by the admin 39 | modifier checkAdmin(address owner) { 40 | require( 41 | owner == admin, 42 | "Only the election admin has access to this function." 43 | ); 44 | _; 45 | } 46 | 47 | //modifiers to check for the states of the election 48 | modifier checkIfCreated() { 49 | require( 50 | electionState == State.CREATED, 51 | "The election is either ongoing or has concluded." 52 | ); 53 | _; 54 | } 55 | 56 | modifier checkIfOngoing() { 57 | require( 58 | electionState == State.ONGOING, 59 | "Election is not active or ongoing currently." 60 | ); 61 | _; 62 | } 63 | 64 | modifier checkIfComplete() { 65 | require(electionState == State.CONCLUDED, "The election has not concluded yet."); 66 | _; 67 | } 68 | 69 | modifier checkNotComplete() { 70 | require(electionState != State.CONCLUDED, "The election has concluded."); 71 | _; 72 | } 73 | 74 | //modifier to check if a voter is a valid voter 75 | modifier checkIfVoterValid(address owner) { 76 | require( 77 | !voters[owner].hasVoted, 78 | "Voter has already voted." 79 | ); 80 | require( 81 | voters[owner].weight > 0, 82 | "Voter has not been registered or already delegated their vote." 83 | ); 84 | _; 85 | } 86 | 87 | //modifier to check if the candidate being voted for is a valid candidate 88 | modifier checkIfCandidateValid(uint256 _candidateId) { 89 | require( 90 | _candidateId > 0 && _candidateId <= candidate_count, 91 | "Invalid candidate." 92 | ); 93 | _; 94 | } 95 | 96 | //modifier to check if the person is not an admin 97 | modifier checkNotAdmin(address owner) { 98 | require( 99 | owner != admin, 100 | "The election admin is not allowed to access this function." 101 | ); 102 | _; 103 | } 104 | 105 | //modifier to check if the voter is not yet registered for the addVoter function 106 | modifier checkNotRegistered(address voter) { 107 | require( 108 | !voters[voter].hasVoted && voters[voter].weight == 0 && !voters[voter].isRegistered, 109 | "Voter has already been registered." 110 | ); 111 | _; 112 | } 113 | 114 | //events to be logged into the blockchain 115 | event AddedAVoter(address voter); 116 | event VotedSuccessfully(uint256 candidateId); 117 | event DelegatedSuccessfully(address delegate); 118 | event ElectionStart(State election_state); 119 | event ElectionEnd(State election_state); 120 | event AddedACandidate(uint candidateID, string candidateName, string candidateProposal); 121 | 122 | // Initialization 123 | constructor(address owner, string memory desc) public { 124 | admin = owner; 125 | electionState = State.CREATED; // Setting Eection state to CREATED 126 | description = desc; 127 | } 128 | 129 | function checkState() public view returns (string memory state) 130 | { 131 | if(electionState == State.CREATED) 132 | return "CREATED"; 133 | else if(electionState == State.ONGOING) 134 | return "ONGOING"; 135 | else if(electionState == State.CONCLUDED) 136 | return "CONCLUDED"; 137 | } 138 | // To Add a candidate 139 | // Only admin can add and 140 | // candidate can be added only before election starts 141 | function addCandidate(string memory _name, string memory _proposal, address owner) 142 | public 143 | checkAdmin(owner) 144 | checkIfCreated 145 | { 146 | candidate_count++; 147 | candidates[candidate_count].ID = candidate_count; 148 | candidates[candidate_count].name = _name; 149 | candidates[candidate_count].proposal = _proposal; 150 | voteCount[candidate_count] = 0; 151 | emit AddedACandidate(candidate_count, _name, _proposal); 152 | } 153 | 154 | // To add a voter 155 | // only admin can add 156 | // can add only before election starts 157 | // can add a voter only one time 158 | function addVoter(address _voter, address owner) 159 | public 160 | checkAdmin(owner) 161 | checkNotAdmin(_voter) 162 | checkIfCreated 163 | checkNotRegistered(_voter) 164 | { 165 | voter_count++; 166 | voterID[voter_count] = _voter; 167 | voters[_voter].weight = 1; 168 | voters[_voter].isRegistered = true; 169 | emit AddedAVoter(_voter); 170 | } 171 | 172 | // setting Election state to ONGOING 173 | // by admin 174 | function startElection(address owner) public checkAdmin(owner) checkIfCreated { 175 | electionState = State.ONGOING; 176 | emit ElectionStart(electionState); 177 | } 178 | 179 | // To display candidates 180 | function displayCandidate(uint256 _ID) 181 | public 182 | view 183 | returns ( 184 | uint256 id, 185 | string memory name, 186 | string memory proposal 187 | ) 188 | { 189 | return ( 190 | candidates[_ID].ID, 191 | candidates[_ID].name, 192 | candidates[_ID].proposal 193 | ); 194 | } 195 | 196 | //Show winner of election 197 | function showWinner() 198 | public 199 | view 200 | checkIfComplete 201 | returns (string memory name, uint256 id, uint256 votes) 202 | { 203 | uint256 max; 204 | uint256 maxIndex; 205 | string memory winner; 206 | for (uint256 i = 1; i <= candidate_count; i++) { 207 | if (voteCount[i] > max) { 208 | winner = candidates[i].name; 209 | maxIndex = i; 210 | max = voteCount[i]; 211 | } 212 | } 213 | return (winner,maxIndex, max) ; 214 | } 215 | 216 | // to delegate the vote 217 | // only during election is going on 218 | // and by a voter who has not yet voted 219 | function delegateVote(address _delegate, address owner) 220 | public 221 | checkNotComplete 222 | checkIfVoterValid(owner) 223 | checkIfVoterValid(_delegate) 224 | checkNotAdmin(_delegate) 225 | checkNotAdmin(owner) 226 | { 227 | require(_delegate != owner, "Self delegation is not allowed."); 228 | address to = _delegate; 229 | while (voters[to].delegate != address(0)) { 230 | to = voters[to].delegate; 231 | 232 | // To prevent infinite loop 233 | if (to == owner) { 234 | revert(); 235 | } 236 | } 237 | voters[owner].delegate = to; 238 | emit DelegatedSuccessfully(_delegate); 239 | voters[owner].hasVoted = true; 240 | 241 | if (voters[to].hasVoted) { 242 | // if delegate has already voted 243 | // voters vote is directly added to candidates vote count 244 | voteCount[voters[to].voteTowards] += voters[owner].weight; 245 | voters[owner].weight = 0; 246 | } else { 247 | voters[to].weight += voters[owner].weight; 248 | voters[owner].weight = 0; 249 | } 250 | } 251 | 252 | // to cast the vote 253 | function vote(uint256 _ID, address owner) 254 | public 255 | checkIfOngoing 256 | checkIfVoterValid(owner) 257 | checkIfCandidateValid(_ID) 258 | { 259 | voters[owner].hasVoted = true; 260 | voters[owner].voteTowards = _ID; 261 | voteCount[_ID] += voters[owner].weight; 262 | voters[owner].weight = 0; 263 | emit VotedSuccessfully(_ID); 264 | } 265 | 266 | // Setting Election state to STOP 267 | // by admin 268 | function endElection(address owner) public checkAdmin(owner) { 269 | electionState = State.CONCLUDED; 270 | emit ElectionEnd(electionState); 271 | } 272 | 273 | // to display result 274 | function showResults(uint256 _ID) 275 | public 276 | view 277 | checkIfComplete 278 | checkIfCandidateValid(_ID) 279 | returns ( 280 | uint256 id, 281 | string memory name, 282 | uint256 count 283 | ) 284 | { 285 | return (_ID, candidates[_ID].name, voteCount[_ID]); 286 | } 287 | 288 | function getVoter(uint ID, address owner) public view checkAdmin(owner) 289 | returns ( 290 | uint256 id, 291 | address voterAddress, 292 | address delegate, 293 | uint256 weight 294 | ) 295 | { 296 | return ( 297 | ID, 298 | voterID[ID], 299 | voters[voterID[ID]].delegate, 300 | voters[voterID[ID]].weight 301 | ); 302 | } 303 | 304 | function voterProfile(address voterAddress) public view 305 | returns ( 306 | uint256 id, 307 | address delegate, 308 | uint256 weight, 309 | uint256 votedTowards, 310 | string memory name 311 | ) 312 | { 313 | 314 | for(uint256 i = 1; i<= voter_count; i++) 315 | { 316 | if(voterAddress == voterID[i]) 317 | { 318 | return ( 319 | i, 320 | voters[voterID[i]].delegate, 321 | voters[voterID[i]].weight, 322 | voters[voterID[i]].voteTowards, 323 | candidates[voters[voterID[i]].voteTowards].name 324 | ); 325 | } 326 | } 327 | } 328 | 329 | } 330 | 331 | 332 | tongue enable magic omit gown cricket bitter summer super obtain monkey random -------------------------------------------------------------------------------- /lib/features/ui/screens/admin_screens/dashboard_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:e_vote/features/auth/presentation/bloc/auth_bloc/auth_bloc.dart'; 2 | import 'package:e_vote/features/auth/presentation/bloc/auth_bloc/auth_events.dart'; 3 | import 'package:e_vote/features/ui/bloc/admin_bloc/admin_bloc.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | import 'package:flutter_icons/flutter_icons.dart'; 7 | 8 | class DashboardScreen extends StatefulWidget { 9 | @override 10 | _DashboardScreenState createState() => _DashboardScreenState(); 11 | } 12 | 13 | class _DashboardScreenState extends State { 14 | AdminBloc adminBloc = AdminBloc(); 15 | @override 16 | Widget build(BuildContext context) { 17 | return BlocProvider( 18 | create: (_) => adminBloc, 19 | child: Container( 20 | child: Scaffold( 21 | appBar: AppBar( 22 | actions: [ 23 | 24 | IconButton( 25 | icon: Icon( 26 | Icons.refresh, 27 | color: Colors.black, 28 | ), 29 | onPressed: () { 30 | adminBloc.add(GetElectionDetails()); 31 | }), 32 | IconButton( 33 | icon: Icon( 34 | MaterialCommunityIcons.logout, 35 | color: Colors.black, 36 | ), 37 | onPressed: () { 38 | BlocProvider.of(context)..add(LoggedOut()); 39 | }), 40 | ], 41 | centerTitle: true, 42 | title: Text( 43 | 'Dashboard', 44 | style: TextStyle(color: Colors.black87), 45 | ), 46 | ), 47 | body: BlocConsumer( 48 | bloc: adminBloc..add(GetElectionDetails()), 49 | listener: (BuildContext context, AdminState state) { 50 | if (state is AdminError) { 51 | Scaffold.of(context).showSnackBar(SnackBar( 52 | backgroundColor: Colors.red[700], 53 | content: Row( 54 | mainAxisAlignment: MainAxisAlignment.start, 55 | children: [ 56 | SizedBox( 57 | width: 10, 58 | ), 59 | Icon(Icons.error), 60 | SizedBox( 61 | width: 10, 62 | ), 63 | Text( 64 | state.errorMessage.message, 65 | style: TextStyle(color: Colors.white), 66 | ), 67 | ]))); 68 | } else if (state is ElectionTxHash) { 69 | Scaffold.of(context).showSnackBar(SnackBar( 70 | backgroundColor: Colors.green, 71 | content: Row( 72 | mainAxisAlignment: MainAxisAlignment.start, 73 | children: [ 74 | Icon( 75 | Icons.check, 76 | color: Colors.white, 77 | ), 78 | SizedBox( 79 | width: 10, 80 | ), 81 | Flexible( 82 | child: Text( 83 | 'TxHash: ' + state.txHash, 84 | style: TextStyle(color: Colors.white), 85 | textAlign: TextAlign.center, 86 | maxLines: 3, 87 | ), 88 | ), 89 | ]))); 90 | } else if (state is Loading) { 91 | Scaffold.of(context).showSnackBar(SnackBar( 92 | backgroundColor: Theme.of(context).primaryColorDark, 93 | content: Row( 94 | mainAxisAlignment: MainAxisAlignment.start, 95 | children: [ 96 | Container( 97 | height: 35, 98 | child: CircularProgressIndicator( 99 | strokeWidth: 2, 100 | valueColor: new AlwaysStoppedAnimation( 101 | Colors.white), 102 | ), 103 | ), 104 | SizedBox( 105 | width: 10, 106 | ), 107 | Flexible( 108 | child: Text( 109 | 'Loading', 110 | style: TextStyle(color: Colors.white), 111 | textAlign: TextAlign.center, 112 | maxLines: 3, 113 | ), 114 | ), 115 | ]))); 116 | } 117 | }, 118 | buildWhen: (previous, current) { 119 | if (current is AdminError || 120 | current is Loading || 121 | current is ElectionTxHash) { 122 | print('lol'); 123 | return false; 124 | } 125 | return true; 126 | }, 127 | builder: (BuildContext context, AdminState state) { 128 | if (state is ElectionDetailsState) { 129 | return Column( 130 | mainAxisAlignment: MainAxisAlignment.center, 131 | children: [ 132 | Center( 133 | child: Text('ADMIN ADDRESS', 134 | style: TextStyle( 135 | fontSize: 20, 136 | fontWeight: FontWeight.w700, 137 | color: Theme.of(context).primaryColor)), 138 | ), 139 | Padding( 140 | padding: const EdgeInsets.symmetric( 141 | vertical: 10.0, horizontal: 12), 142 | child: Center( 143 | child: SelectableText(state.adminAddress, 144 | toolbarOptions: ToolbarOptions( 145 | copy: true 146 | ), 147 | textAlign: TextAlign.center, 148 | style: TextStyle( 149 | fontSize: 20, 150 | fontWeight: FontWeight.w700, 151 | color: Theme.of(context).accentColor)), 152 | ), 153 | ), 154 | SizedBox( 155 | height: 20, 156 | ), 157 | Center( 158 | child: Text('DESCRIPTION', 159 | style: TextStyle( 160 | fontSize: 20, 161 | fontWeight: FontWeight.w700, 162 | color: Theme.of(context).primaryColor)), 163 | ), 164 | Padding( 165 | padding: const EdgeInsets.symmetric( 166 | vertical: 10.0, horizontal: 12), 167 | child: Center( 168 | child: Text(state.description, 169 | textAlign: TextAlign.center, 170 | style: TextStyle( 171 | fontSize: 20, 172 | fontWeight: FontWeight.w700, 173 | color: Theme.of(context).accentColor))), 174 | ), 175 | SizedBox( 176 | height: 20, 177 | ), 178 | Center( 179 | child: Text('ELECTION STATE', 180 | style: TextStyle( 181 | fontSize: 20, 182 | fontWeight: FontWeight.w700, 183 | color: Theme.of(context).primaryColor)), 184 | ), 185 | Padding( 186 | padding: const EdgeInsets.symmetric( 187 | vertical: 10.0, horizontal: 12), 188 | child: Center( 189 | child: Text(state.electionState, 190 | textAlign: TextAlign.center, 191 | style: TextStyle( 192 | fontSize: 20, 193 | fontWeight: FontWeight.w700, 194 | color: Theme.of(context).accentColor))), 195 | ), 196 | SizedBox( 197 | height: 20, 198 | ), 199 | if (state.electionState == "CREATED") 200 | Container( 201 | height: 50, 202 | width: 200, 203 | child: FlatButton( 204 | color: Theme.of(context).primaryColor, 205 | shape: RoundedRectangleBorder( 206 | borderRadius: BorderRadius.circular(10)), 207 | onPressed: () { 208 | adminBloc..add(StartElection()); 209 | }, 210 | child: Text( 211 | 'Start the Election', 212 | style: TextStyle( 213 | fontSize: 18, 214 | fontWeight: FontWeight.w700), 215 | )), 216 | ) 217 | else if (state.electionState == "ONGOING") 218 | Container( 219 | height: 50, 220 | width: 200, 221 | child: FlatButton( 222 | color: Theme.of(context).primaryColor, 223 | shape: RoundedRectangleBorder( 224 | borderRadius: BorderRadius.circular(10)), 225 | onPressed: () { 226 | BlocProvider.of(context) 227 | ..add(EndElection()); 228 | }, 229 | child: Text( 230 | 'End the Election', 231 | style: TextStyle( 232 | fontSize: 18, 233 | fontWeight: FontWeight.w700), 234 | )), 235 | ) 236 | ], 237 | ); 238 | } else { 239 | BlocProvider.of(context) 240 | ..add(GetElectionDetails()); 241 | return Center( 242 | child: CircularProgressIndicator( 243 | valueColor: new AlwaysStoppedAnimation( 244 | Theme.of(context).primaryColor), 245 | ), 246 | ); 247 | } 248 | }, 249 | ))), 250 | ); 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /lib/features/ui/screens/voter_screens/voter_profile.dart: -------------------------------------------------------------------------------- 1 | import 'package:e_vote/features/auth/presentation/bloc/auth_bloc/auth_barrel_bloc.dart'; 2 | import 'package:e_vote/features/ui/bloc/voter_bloc/voter_bloc.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | import 'package:flutter_icons/flutter_icons.dart'; 6 | 7 | class VoterProfile extends StatefulWidget { 8 | @override 9 | _VoterProfileState createState() => _VoterProfileState(); 10 | } 11 | 12 | class _VoterProfileState extends State { 13 | VoterBloc voterBloc; 14 | @override 15 | void initState() { 16 | voterBloc = VoterBloc(); 17 | super.initState(); 18 | } 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return BlocProvider( 23 | create: (_) => voterBloc, 24 | child: Container( 25 | child: Scaffold( 26 | appBar: AppBar( 27 | actions: [ 28 | IconButton( 29 | icon: Icon( 30 | Icons.refresh, 31 | color: Colors.black, 32 | ), 33 | onPressed: () { 34 | voterBloc.add(ShowElectionDetails()); 35 | }), 36 | IconButton( 37 | icon: Icon( 38 | MaterialCommunityIcons.logout, 39 | color: Colors.black, 40 | ), 41 | onPressed: () { 42 | BlocProvider.of(context)..add(LoggedOut()); 43 | }), 44 | ], 45 | centerTitle: true, 46 | title: Text( 47 | 'My Profile', 48 | style: TextStyle(color: Colors.black87), 49 | ), 50 | ), 51 | body: Container( 52 | padding: 53 | EdgeInsets.only(top: 10, bottom: 10, left: 10, right: 10), 54 | decoration: 55 | BoxDecoration(borderRadius: BorderRadius.circular(10)), 56 | child: BlocConsumer( 57 | bloc: voterBloc..add(GetVoterProfile()), 58 | builder: (BuildContext context, VoterState state) { 59 | if (state is VoterProfileState) { 60 | print('lol'); 61 | return Card( 62 | elevation: 0.3, 63 | shape: RoundedRectangleBorder( 64 | borderRadius: BorderRadius.circular(10)), 65 | child: Padding( 66 | padding: const EdgeInsets.all(10.0), 67 | child: ListView(children: [ 68 | Container( 69 | padding: EdgeInsets.all(10), 70 | decoration: BoxDecoration( 71 | shape: BoxShape.circle, 72 | border: Border.all( 73 | color: Theme.of(context).primaryColor, 74 | width: 1.5)), 75 | child: Center( 76 | child: Icon(Icons.person, 77 | size: 170, 78 | color: Theme.of(context).accentColor), 79 | ), 80 | ), 81 | SizedBox( 82 | height: 20, 83 | ), 84 | Center( 85 | child: Text('VOTER ID', 86 | style: TextStyle( 87 | fontSize: 20, 88 | fontWeight: FontWeight.w700, 89 | color: Theme.of(context).primaryColor)), 90 | ), 91 | Padding( 92 | padding: const EdgeInsets.symmetric( 93 | vertical: 10.0, horizontal: 12), 94 | child: Center( 95 | child: Text( 96 | state.voterProfile.id == 0 97 | ? "Unregistered" 98 | : state.voterProfile.id.toString(), 99 | textAlign: TextAlign.center, 100 | style: TextStyle( 101 | fontSize: 20, 102 | fontWeight: FontWeight.w700, 103 | color: Theme.of(context) 104 | .accentColor))), 105 | ), 106 | SizedBox( 107 | height: 20, 108 | ), 109 | Center( 110 | child: Text('VOTER ADDRESS', 111 | style: TextStyle( 112 | fontSize: 20, 113 | fontWeight: FontWeight.w700, 114 | color: Theme.of(context).primaryColor)), 115 | ), 116 | Padding( 117 | padding: const EdgeInsets.symmetric( 118 | vertical: 10.0, horizontal: 12), 119 | child: Center( 120 | child: SelectableText( 121 | 122 | state.voterProfile.address.toString(), 123 | toolbarOptions: ToolbarOptions( 124 | copy: true 125 | ), 126 | textAlign: TextAlign.center, 127 | style: TextStyle( 128 | fontSize: 20, 129 | fontWeight: FontWeight.w700, 130 | color: Theme.of(context) 131 | .accentColor))), 132 | ), 133 | SizedBox( 134 | height: 20, 135 | ), 136 | Center( 137 | child: Text('VOTER DELEGATE ADDRESS', 138 | style: TextStyle( 139 | fontSize: 20, 140 | fontWeight: FontWeight.w700, 141 | color: Theme.of(context).primaryColor)), 142 | ), 143 | Padding( 144 | padding: const EdgeInsets.symmetric( 145 | vertical: 10.0, horizontal: 12), 146 | child: Center( 147 | child: Text( 148 | BigInt.parse(state.voterProfile 149 | .delegateAddress) 150 | .toInt() == 151 | 0 152 | ? "Not Delegated" 153 | : state.voterProfile.delegateAddress 154 | .toString(), 155 | textAlign: TextAlign.center, 156 | style: TextStyle( 157 | fontSize: 20, 158 | fontWeight: FontWeight.w700, 159 | color: Theme.of(context) 160 | .accentColor))), 161 | ), 162 | SizedBox( 163 | height: 20, 164 | ), 165 | Center( 166 | child: Text('VOTER WEIGHT', 167 | style: TextStyle( 168 | fontSize: 20, 169 | fontWeight: FontWeight.w700, 170 | color: Theme.of(context).primaryColor)), 171 | ), 172 | Padding( 173 | padding: const EdgeInsets.symmetric( 174 | vertical: 10.0, horizontal: 12), 175 | child: Center( 176 | child: Text( 177 | state.voterProfile.weight == 0 178 | ? ( BigInt.parse(state.voterProfile 179 | .delegateAddress) 180 | .toInt() == 181 | 0 ? "Unregistered" : "Vote delegated") 182 | : state.voterProfile.weight 183 | .toString(), 184 | textAlign: TextAlign.center, 185 | style: TextStyle( 186 | fontSize: 20, 187 | fontWeight: FontWeight.w700, 188 | color: Theme.of(context) 189 | .accentColor))), 190 | ), 191 | SizedBox( 192 | height: 20, 193 | ), 194 | Center( 195 | child: Text('VOTED TOWARDS', 196 | style: TextStyle( 197 | fontSize: 20, 198 | fontWeight: FontWeight.w700, 199 | color: Theme.of(context).primaryColor)), 200 | ), 201 | Padding( 202 | padding: const EdgeInsets.symmetric( 203 | vertical: 10.0, horizontal: 12), 204 | child: Center( 205 | child: Text( 206 | state.voterProfile.voteTowards == 0 207 | ? ( BigInt.parse(state.voterProfile 208 | .delegateAddress) 209 | .toInt() == 210 | 0 ? "Not voted yet" : "Vote delegated") 211 | : state.voterProfile.voteTowards 212 | .toString(), 213 | textAlign: TextAlign.center, 214 | style: TextStyle( 215 | fontSize: 20, 216 | fontWeight: FontWeight.w700, 217 | color: Theme.of(context) 218 | .accentColor))), 219 | ), 220 | SizedBox( 221 | height: 20, 222 | ), 223 | ])), 224 | ); 225 | } else { 226 | BlocProvider.of(context) 227 | ..add(GetVoterProfile()); 228 | return Center( 229 | child: CircularProgressIndicator( 230 | valueColor: new AlwaysStoppedAnimation( 231 | Theme.of(context).primaryColor), 232 | ), 233 | ); 234 | } 235 | }, 236 | listener: (BuildContext context, VoterState state) {})), 237 | ))); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /lib/features/ui/screens/voter_screens/election_results_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:e_vote/features/auth/presentation/bloc/auth_bloc/auth_barrel_bloc.dart'; 2 | import 'package:e_vote/features/ui/bloc/voter_bloc/voter_bloc.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | import 'package:flutter_icons/flutter_icons.dart'; 6 | 7 | class ElectionResults extends StatefulWidget { 8 | @override 9 | _ElectionResultsState createState() => _ElectionResultsState(); 10 | } 11 | 12 | class _ElectionResultsState extends State { 13 | VoterBloc voterBloc; 14 | @override 15 | void initState() { 16 | voterBloc = VoterBloc(); 17 | super.initState(); 18 | } 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return BlocProvider( 23 | create: (_) => voterBloc, 24 | child: Container( 25 | child: Scaffold( 26 | appBar: AppBar( 27 | actions: [ 28 | IconButton( 29 | icon: Icon( 30 | Icons.refresh, 31 | color: Colors.black, 32 | ), 33 | onPressed: () { 34 | voterBloc.add(ShowResults()); 35 | }), 36 | IconButton( 37 | icon: Icon( 38 | MaterialCommunityIcons.logout, 39 | color: Colors.black, 40 | ), 41 | onPressed: () { 42 | BlocProvider.of(context)..add(LoggedOut()); 43 | }) 44 | ], 45 | centerTitle: true, 46 | title: Text( 47 | 'Election Results', 48 | style: TextStyle(color: Colors.black87), 49 | ), 50 | ), 51 | body: BlocConsumer( 52 | bloc: voterBloc..add(ShowResults()), 53 | buildWhen: (VoterState prev, VoterState curr) { 54 | if (curr is Loading) return false; 55 | return true; 56 | }, 57 | builder: (BuildContext context, VoterState state) { 58 | if (state is VoterError) { 59 | return Padding( 60 | padding: const EdgeInsets.only(right: 8.0, left: 8.0), 61 | child: Center( 62 | child: Column( 63 | mainAxisAlignment: MainAxisAlignment.center, 64 | children: [ 65 | Flexible( 66 | child: Text( 67 | state.errorMessage.message, 68 | textAlign: TextAlign.center, 69 | style: TextStyle( 70 | color: Theme.of(context).primaryColor, 71 | fontSize: 20, 72 | fontWeight: FontWeight.w700), 73 | ), 74 | ), 75 | SizedBox( 76 | height: 20, 77 | ), 78 | Image.asset( 79 | "assets/404-error.png", 80 | height: 100, 81 | width: 100, 82 | ) 83 | ]), 84 | ), 85 | ); 86 | } else if (state is Results) { 87 | return Padding( 88 | padding: 89 | const EdgeInsets.only(right: 8.0, left: 8.0, top: 20), 90 | child: Column( 91 | mainAxisAlignment: MainAxisAlignment.start, 92 | crossAxisAlignment: CrossAxisAlignment.start, 93 | children: [ 94 | Container( 95 | height: 200, 96 | width: 400, 97 | child: Column( 98 | children: [ 99 | Image.asset("assets/trophy.png", 100 | height: 100, width: 100), 101 | SizedBox( 102 | height: 10, 103 | ), 104 | Card( 105 | elevation: 0.3, 106 | color: Color(0xFFFAFAFA), 107 | shape: RoundedRectangleBorder( 108 | side: BorderSide( 109 | color: Color(0xFFDCDCDC), width: 0.5), 110 | borderRadius: BorderRadius.circular(8)), 111 | child: Padding( 112 | padding: const EdgeInsets.all(8.0), 113 | child: Column( 114 | crossAxisAlignment: 115 | CrossAxisAlignment.start, 116 | children: [ 117 | Row( 118 | crossAxisAlignment: 119 | CrossAxisAlignment.start, 120 | children: [ 121 | Text( 122 | 'Candidate ID: ', 123 | style: TextStyle( 124 | color: Color(0xff2F2F2F), 125 | fontSize: 19, 126 | fontWeight: FontWeight.w600), 127 | ), 128 | Flexible( 129 | child: Text( 130 | state.winner.id.toString(), 131 | style: TextStyle( 132 | color: Color(0xff495057), 133 | fontSize: 19, 134 | fontWeight: 135 | FontWeight.w400)), 136 | ) 137 | ], 138 | ), 139 | Row( 140 | crossAxisAlignment: 141 | CrossAxisAlignment.start, 142 | children: [ 143 | Text('Name: ', 144 | style: TextStyle( 145 | color: Color(0xff2F2F2F), 146 | fontSize: 19, 147 | fontWeight: 148 | FontWeight.w600)), 149 | Flexible( 150 | child: Text( 151 | state.winner.name.toString(), 152 | style: TextStyle( 153 | color: Color(0xff2F2F2F), 154 | fontSize: 19, 155 | fontWeight: 156 | FontWeight.w400)), 157 | ) 158 | ], 159 | ), 160 | ]), 161 | ), 162 | ) 163 | ], 164 | ), 165 | ), 166 | Padding( 167 | padding: const EdgeInsets.all(8.0), 168 | child: Text( 169 | 'RESULTS', 170 | textAlign: TextAlign.left, 171 | style: TextStyle( 172 | fontSize: 20, 173 | fontWeight: FontWeight.w600, 174 | ), 175 | ), 176 | ), 177 | ListView.builder( 178 | shrinkWrap: true, 179 | padding: EdgeInsets.all(8), 180 | itemCount: state.results.length, 181 | itemBuilder: (BuildContext context, int i) { 182 | return Card( 183 | elevation: 0.3, 184 | color: Color(0xFFFAFAFA), 185 | shape: RoundedRectangleBorder( 186 | side: BorderSide( 187 | color: Color(0xFFDCDCDC), width: 0.5), 188 | borderRadius: BorderRadius.circular(8)), 189 | child: Padding( 190 | padding: const EdgeInsets.all(8.0), 191 | child: Column( 192 | crossAxisAlignment: 193 | CrossAxisAlignment.start, 194 | children: [ 195 | Row( 196 | crossAxisAlignment: 197 | CrossAxisAlignment.start, 198 | children: [ 199 | Text( 200 | 'Candidate ID: ', 201 | style: TextStyle( 202 | color: Color(0xff2F2F2F), 203 | fontSize: 19, 204 | fontWeight: FontWeight.w600), 205 | ), 206 | Flexible( 207 | child: Text( 208 | state.results[i].id 209 | .toString(), 210 | style: TextStyle( 211 | color: Color(0xff495057), 212 | fontSize: 19, 213 | fontWeight: 214 | FontWeight.w400)), 215 | ) 216 | ], 217 | ), 218 | Row( 219 | crossAxisAlignment: 220 | CrossAxisAlignment.start, 221 | children: [ 222 | Text('Name: ', 223 | style: TextStyle( 224 | color: Color(0xff2F2F2F), 225 | fontSize: 19, 226 | fontWeight: 227 | FontWeight.w600)), 228 | Flexible( 229 | child: Text( 230 | state.results[i].name 231 | .toString(), 232 | style: TextStyle( 233 | color: Color(0xff2F2F2F), 234 | fontSize: 19, 235 | fontWeight: 236 | FontWeight.w400)), 237 | ) 238 | ], 239 | ), 240 | Row( 241 | crossAxisAlignment: 242 | CrossAxisAlignment.start, 243 | children: [ 244 | Text('Vote Count: ', 245 | style: TextStyle( 246 | color: Color(0xff2F2F2F), 247 | fontSize: 19, 248 | fontWeight: 249 | FontWeight.w600)), 250 | Flexible( 251 | child: Text( 252 | state.results[i].count 253 | .toString(), 254 | style: TextStyle( 255 | color: Color(0xff2F2F2F), 256 | fontSize: 19, 257 | fontWeight: 258 | FontWeight.w400)), 259 | ) 260 | ], 261 | ) 262 | ]), 263 | ), 264 | ); 265 | }) 266 | ], 267 | ), 268 | ); 269 | } else { 270 | return Center( 271 | child: CircularProgressIndicator( 272 | valueColor: new AlwaysStoppedAnimation( 273 | Theme.of(context).primaryColor), 274 | ), 275 | ); 276 | } 277 | }, 278 | listener: (BuildContext context, VoterState state) {}), 279 | ), 280 | ), 281 | ); 282 | } 283 | } 284 | --------------------------------------------------------------------------------