├── test └── quickfire_test.dart ├── .gitignore ├── pubspec.yaml ├── CHANGELOG.md ├── bin └── quickfire.dart ├── lib ├── commands │ ├── build_project.dart │ ├── sha_generate.dart │ ├── publish_project.dart │ └── create_project.dart ├── core │ ├── create_project │ │ ├── assets_handler.dart │ │ ├── stripe_handler.dart │ │ ├── notification_handler.dart │ │ ├── on_boarding_creation.dart │ │ ├── command_handler.dart │ │ ├── main_handler.dart │ │ └── auth_handler.dart │ └── deploy_project │ │ ├── pubspec_handler.dart │ │ ├── sha_handler.dart │ │ ├── gradle_handler.dart │ │ └── keystore_handler.dart └── tools │ ├── cli_handler.dart │ └── choice_selctor.dart ├── analysis_options.yaml ├── LICENSE ├── README.md └── pubspec.lock /test/quickfire_test.dart: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # https://dart.dev/guides/libraries/private-files 2 | # Created by `dart pub` 3 | .dart_tool/ 4 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: quickfire 2 | description: auth, Bloc, onboarding, Payment, UI and Play Store deployment, effortlessly woven for an elevated development experience. 3 | version: 1.0.8 4 | repository: https://github.com/FOSS-Community/quickfire 5 | 6 | environment: 7 | sdk: ^3.1.5 8 | 9 | # Add regular dependencies here. 10 | dependencies: 11 | args: ^2.4.2 12 | http: ^1.1.2 13 | path: ^1.8.3 14 | yaml: ^3.1.2 15 | yaml_edit: ^2.1.1 16 | 17 | dev_dependencies: 18 | lints: ^2.0.0 19 | test: ^1.21.0 20 | 21 | executables: 22 | quickfire: quickfire 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.8 2 | 3 | - Fix: Widgets not initialized when no authentication is selected 4 | 5 | 6 | ## 1.0.7 7 | 8 | - Changed Compile SDK Version to 34 9 | 10 | ## 1.0.6 11 | 12 | - Feat: Added Command to generate Debug SHA Keys. 13 | 14 | ## 1.0.5 15 | 16 | - Feat: Added Deployment Support to Google Play Store. 17 | 18 | ## 1.0.4 19 | 20 | - Fix: No need to add $ symbols manually 21 | 22 | ## 1.0.3 23 | 24 | - Fix: Notification Services 25 | 26 | ## 1.0.2 27 | 28 | - Fix: main.dart when no auth service is selected 29 | 30 | ## 1.0.1 31 | 32 | - Added menu driven flow 33 | 34 | ## 1.0.0 35 | 36 | - Initial version. 37 | -------------------------------------------------------------------------------- /bin/quickfire.dart: -------------------------------------------------------------------------------- 1 | import 'package:args/command_runner.dart'; 2 | import 'package:quickfire/commands/create_project.dart'; 3 | import 'package:quickfire/commands/build_project.dart'; 4 | import 'package:quickfire/commands/publish_project.dart'; 5 | import 'package:quickfire/commands/sha_generate.dart'; 6 | 7 | Future main(List args) async { 8 | final CommandRunner runner = 9 | CommandRunner('quickfire', 'Flutter on steroids') 10 | ..addCommand(CreateProject()) 11 | ..addCommand(BuildProject()) 12 | ..addCommand(PublishProject()) 13 | ..addCommand(GenerateSHA()); 14 | 15 | try { 16 | await runner.run(args); 17 | } catch (e) { 18 | print('Error: $e'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/commands/build_project.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:args/command_runner.dart'; 4 | import 'package:quickfire/core/deploy_project/pubspec_handler.dart'; 5 | import 'package:quickfire/tools/cli_handler.dart'; 6 | 7 | class BuildProject extends Command { 8 | @override 9 | String get name => 'build'; 10 | 11 | @override 12 | String get description => 'Build app bundle'; 13 | 14 | // Handle method 15 | @override 16 | Future run() async { 17 | final cliHanldler = CliHandler(); 18 | cliHanldler.clearScreen(); 19 | cliHanldler.printBoltCyanText('Updated Your App Version code'); 20 | final currentVersion = PubspecHandler.getVersion(); 21 | PubspecHandler.updatePubspecValue('1.0.0+${currentVersion + 1}'); 22 | 23 | final ProcessResult buildAppBundle = await Process.run( 24 | 'flutter', 25 | ['build', 'appbundle'], 26 | ); 27 | if (buildAppBundle.exitCode != 0) { 28 | print('Error building appbundle!'); 29 | print(buildAppBundle.stderr); 30 | return; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the static analysis results for your project (errors, 2 | # warnings, and lints). 3 | # 4 | # This enables the 'recommended' set of lints from `package:lints`. 5 | # This set helps identify many issues that may lead to problems when running 6 | # or consuming Dart code, and enforces writing Dart using a single, idiomatic 7 | # style and format. 8 | # 9 | # If you want a smaller set of lints you can change this to specify 10 | # 'package:lints/core.yaml'. These are just the most critical lints 11 | # (the recommended set includes the core lints). 12 | # The core lints are also what is used by pub.dev for scoring packages. 13 | 14 | include: package:lints/recommended.yaml 15 | 16 | # Uncomment the following section to specify additional rules. 17 | 18 | # linter: 19 | # rules: 20 | # - camel_case_types 21 | 22 | # analyzer: 23 | # exclude: 24 | # - path/to/excluded/files/** 25 | 26 | # For more information about the core and recommended set of lints, see 27 | # https://dart.dev/go/core-lints 28 | 29 | # For additional information about configuring this file, see 30 | # https://dart.dev/guides/language/analysis-options 31 | -------------------------------------------------------------------------------- /lib/commands/sha_generate.dart: -------------------------------------------------------------------------------- 1 | import 'package:args/command_runner.dart'; 2 | import 'package:quickfire/core/deploy_project/sha_handler.dart'; 3 | import 'package:quickfire/tools/choice_selctor.dart'; 4 | import 'package:quickfire/tools/cli_handler.dart'; 5 | 6 | class GenerateSHA extends Command { 7 | @override 8 | String get name => 'sha'; 9 | 10 | @override 11 | String get description => 'Generate SHA Keys'; 12 | 13 | @override 14 | Future run() async { 15 | final cliHandler = CliHandler(); 16 | 17 | cliHandler.printBoltCyanText('Generating debug SHA keys'); 18 | print('\n'); 19 | final osChoice = ['windows', 'linux', 'mac']; 20 | final osChoiceSelector = ChoiceSelector(osChoice); 21 | cliHandler.printBoltCyanText('Choose your operating system : '); 22 | osChoiceSelector.printOptions(); 23 | osChoiceSelector.handleArrowKeys(); 24 | int osChoiceIndex = osChoiceSelector.selectedIndexForOptions; 25 | // windows 26 | if (osChoiceIndex == 0) { 27 | await SHAHandler.generateWindowsSHA(); 28 | } 29 | // linux 30 | else if (osChoiceIndex == 1) { 31 | await SHAHandler.generateLinuxSHA(); 32 | } 33 | // mac 34 | else if (osChoiceIndex == 2) { 35 | await SHAHandler.generateMacSHA(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/core/create_project/assets_handler.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:http/http.dart' as http; 3 | 4 | class AssetsHandler { 5 | static Future handleAssets() async { 6 | stdout.write('func'); 7 | // create assets folder 8 | final assetsDirectory = Directory('assets'); 9 | assetsDirectory.createSync(); 10 | 11 | // create img folder 12 | final imgDirectory = Directory('assets/img'); 13 | imgDirectory.createSync(); 14 | 15 | http.Response response = 16 | await http.get(Uri.parse('https://iili.io/JzWnaHB.png')); 17 | 18 | final File destinationFile = File('assets/img/logo.png'); 19 | await destinationFile.writeAsBytes(response.bodyBytes); 20 | } 21 | 22 | static void replaceAssetsBlock() { 23 | try { 24 | String newAssetsBlock = ''' 25 | assets: 26 | - assets/img/ 27 | '''; 28 | // Read the content of the pubspec.yaml file 29 | File file = File('pubspec.yaml'); 30 | String content = file.readAsStringSync(); 31 | 32 | // Replace the #assets block with the new assets block 33 | content = content.replaceAll( 34 | RegExp(r'#\s*assets:(.*?)(?=#|$)', multiLine: true, dotAll: true), 35 | newAssetsBlock); 36 | 37 | // Write the modified content back to the file 38 | file.writeAsStringSync(content); 39 | } catch (e) { 40 | print('Error: $e'); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-clause license 2 | 3 | Copyright (c) 2023 utkarshshrivastava.site 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 10 | 11 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /lib/core/deploy_project/pubspec_handler.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:yaml/yaml.dart'; 4 | import 'package:yaml_edit/yaml_edit.dart'; 5 | 6 | class PubspecHandler { 7 | static Future updatePubspecValue(String newValue) async { 8 | // Construct the path to pubspec.yaml 9 | final pubspecPath = 'pubspec.yaml'; 10 | 11 | // Read the contents of pubspec.yaml 12 | final pubspecFile = File(pubspecPath); 13 | final content = pubspecFile.readAsStringSync(); 14 | 15 | // Parse the YAML content using YamlEditor 16 | final editor = YamlEditor(content); 17 | 18 | // Update the value associated with the key 19 | editor.update(['version'], newValue); 20 | 21 | // Write the updated content back to pubspec.yaml 22 | pubspecFile.writeAsStringSync(editor.toString()); 23 | } 24 | 25 | static int getVersion() { 26 | // Construct the path to pubspec.yaml 27 | final pubspecPath = 'pubspec.yaml'; 28 | 29 | // Read the contents of pubspec.yaml 30 | final pubspecFile = File(pubspecPath); 31 | final content = pubspecFile.readAsStringSync(); 32 | 33 | // Parse the YAML content using the yaml package 34 | final pubspecYaml = loadYaml(content); 35 | 36 | // Retrieve the current value of 'version' 37 | final versionValue = pubspecYaml['version']; 38 | final String version = versionValue.toString(); 39 | List parts = version.split('+'); 40 | String numberAfterPlus = parts[1]; 41 | int number = int.tryParse(numberAfterPlus) ?? 0; 42 | 43 | return number; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/tools/cli_handler.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | class CliHandler { 5 | late Timer timer; 6 | int frameIndex = 0; 7 | final animationFrames = ['-', '\\', '|', '/']; 8 | void clearScreen() { 9 | print('\x1B[2J\x1B[0;0H'); 10 | } 11 | 12 | void printWithLoadingAmimation(String message) { 13 | stdout.write('$message '); 14 | 15 | timer = Timer.periodic(Duration(milliseconds: 100), (Timer t) { 16 | stdout.write(animationFrames[frameIndex]); 17 | stdout.write('\b'); // Move the cursor back one position 18 | 19 | frameIndex = (frameIndex + 1) % animationFrames.length; 20 | }); 21 | } 22 | 23 | void stopLoadingAnimation() { 24 | timer.cancel(); 25 | stdout.write('\r'); // Move the cursor to the beginning of the line 26 | stdout.write( 27 | ' ' * (animationFrames[frameIndex].length + 1)); // Clear the line 28 | stdout.write('\r'); // Move the cursor back to the beginning of the line 29 | print(''); // Move to the next line 30 | } 31 | 32 | void eraseLastLine() { 33 | stdout.write('\x1B[1A'); // Move the cursor up one line 34 | stdout.write('\x1B[K'); // Clear the line 35 | } 36 | 37 | void eraseSecondLastLine() { 38 | stdout.write('\x1B[2A'); // Move the cursor up two lines 39 | stdout.write('\x1B[K'); // Clear the line 40 | } 41 | 42 | void printErrorText(String message) { 43 | print('\x1B[1;31m$message\x1B[0m '); 44 | } 45 | 46 | void printBoldGreenText(String message) { 47 | print('\x1B[1;32m$message\x1B[0m '); 48 | } 49 | 50 | void printBoltCyanText(String message) { 51 | print('\x1B[1;36m$message\x1B[0m '); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/tools/choice_selctor.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:quickfire/tools/cli_handler.dart'; 4 | 5 | class ChoiceSelector { 6 | List options; 7 | int selectedIndex; 8 | 9 | ChoiceSelector(this.options) : selectedIndex = 0; 10 | 11 | int get selectedIndexForOptions => selectedIndex; // Getter method 12 | 13 | void printOptions() { 14 | for (int i = 0; i < options.length; i++) { 15 | if (i == selectedIndex) { 16 | print('* ${options[i]}'); 17 | } else { 18 | print(' ${options[i]}'); 19 | } 20 | } 21 | } 22 | 23 | void handleArrowKeys() { 24 | final cliHandler = CliHandler(); 25 | stdin.lineMode = false; 26 | stdin.echoMode = false; 27 | 28 | while (true) { 29 | if (stdin.hasTerminal) { 30 | var key = stdin.readByteSync(); 31 | if (key == 27) { 32 | // Arrow key sequence 33 | var arrowKey = stdin.readByteSync(); 34 | if (arrowKey == 91) { 35 | var direction = stdin.readByteSync(); 36 | if (direction == 65 && selectedIndex > 0) { 37 | // Up arrow 38 | selectedIndex--; 39 | } else if (direction == 66 && selectedIndex < options.length - 1) { 40 | // Down arrow 41 | selectedIndex++; 42 | } 43 | cliHandler.clearScreen(); 44 | printOptions(); 45 | } 46 | } else if (key == 10) { 47 | // Enter key 48 | break; 49 | } 50 | } 51 | } 52 | cliHandler.clearScreen(); 53 | // print('\nSelected Option: ${options[selectedIndex]}'); 54 | stdin.echoMode = true; 55 | stdin.lineMode = true; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/commands/publish_project.dart: -------------------------------------------------------------------------------- 1 | import 'package:args/command_runner.dart'; 2 | import 'package:quickfire/core/deploy_project/gradle_handler.dart'; 3 | import 'package:quickfire/core/deploy_project/keystore_handler.dart'; 4 | import 'package:quickfire/tools/choice_selctor.dart'; 5 | import 'package:quickfire/tools/cli_handler.dart'; 6 | 7 | class PublishProject extends Command { 8 | @override 9 | String get name => 'publish'; 10 | 11 | @override 12 | String get description => 'Make your app publish ready for playstore'; 13 | 14 | @override 15 | Future run() async { 16 | final cliHandler = CliHandler(); 17 | cliHandler.clearScreen(); 18 | cliHandler 19 | .printBoltCyanText('Making your app publish ready with quickfire :'); 20 | 21 | print('\n'); 22 | final osChoice = ['windows', 'linux', 'mac']; 23 | final osChoiceSelector = ChoiceSelector(osChoice); 24 | cliHandler.printBoltCyanText('Choose your operating system : '); 25 | osChoiceSelector.printOptions(); 26 | osChoiceSelector.handleArrowKeys(); 27 | int osChoiceIndex = osChoiceSelector.selectedIndexForOptions; 28 | // windows 29 | if (osChoiceIndex == 0) { 30 | KeystoreHandler.generateWndowsKeystore(); 31 | } 32 | // linux 33 | else if (osChoiceIndex == 1) { 34 | KeystoreHandler.generateLinuxMacUploadKeystore(); 35 | } 36 | // mac 37 | else if (osChoiceIndex == 2) { 38 | KeystoreHandler.generateLinuxMacUploadKeystore(); 39 | } 40 | 41 | cliHandler.stopLoadingAnimation(); 42 | 43 | await KeystoreHandler.generateKeyProperties(); 44 | await GradleHandler.referenceKeyStoreInGradle(); 45 | await GradleHandler.updateBuildGradle(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/core/deploy_project/sha_handler.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | class SHAHandler { 4 | static Future generateWindowsSHA() async { 5 | String command = 6 | 'keytool -list -v -keystore "\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass android'; 7 | 8 | // Start the process 9 | Process process = await Process.start( 10 | '/bin/sh', 11 | ['-c', command], 12 | ); 13 | 14 | // Capture and display the output 15 | stdout.addStream(process.stdout); 16 | stderr.addStream(process.stderr); 17 | 18 | // Wait for the process to complete 19 | int exitCode = await process.exitCode; 20 | print('Command exited with code $exitCode'); 21 | } 22 | 23 | static Future generateLinuxSHA() async { 24 | String command = 25 | 'keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android'; 26 | 27 | // Start the process 28 | Process process = await Process.start( 29 | '/bin/sh', 30 | ['-c', command], 31 | ); 32 | 33 | // Capture and display the output 34 | stdout.addStream(process.stdout); 35 | stderr.addStream(process.stderr); 36 | 37 | // Wait for the process to complete 38 | int exitCode = await process.exitCode; 39 | print('Command exited with code $exitCode'); 40 | } 41 | 42 | static Future generateMacSHA() async { 43 | String command = 44 | 'keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android'; 45 | 46 | // Start the process 47 | Process process = await Process.start( 48 | '/bin/sh', 49 | ['-c', command], 50 | ); 51 | 52 | // Capture and display the output 53 | stdout.addStream(process.stdout); 54 | stderr.addStream(process.stderr); 55 | 56 | // Wait for the process to complete 57 | int exitCode = await process.exitCode; 58 | print('Command exited with code $exitCode'); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quickfire 🔥 : auth, Bloc, onboarding, Payment, UI and Play Store deployment, effortlessly woven for an elevated development experience. 2 | 3 | Tested on Android. 4 | IOS?? Try and tell us :p 5 | 6 | --- 7 | 8 | Craft a feature-rich Flutter project with auth, Bloc architecture, and onboarding screens, effortlessly deployable on the Play Store without manual hassles! 9 | 10 | [![GitHub issues](https://img.shields.io/github/issues/FOSS-Community/quickfire)](https://github.com/FOSS-Community/quickfire) 11 | [![GitHub forks](https://img.shields.io/github/forks/FOSS-Community/quickfire)](https://github.com/FOSS-Community/quickfire) 12 | [![GitHub stars](https://img.shields.io/github/stars/FOSS-Community/quickfire)](https://github.com/FOSS-Community/quickfire/stargazers) 13 | [![GitHub license](https://img.shields.io/github/license/FOSS-Community/quickfire)](https://github.com/FOSS-Community/quickfire/blob/main/LICENSE) 14 | [![Open Source Love svg1](https://badges.frapsoft.com/os/v1/open-source.svg?v=103)](https://github.com/ellerbrock/open-source-badges/) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com) ![contributions welcome](https://img.shields.io/static/v1.svg?label=Contributions&message=Welcome&color=0059b3&style=flat-square) ![GitHub contributors](https://img.shields.io/github/contributors-anon/FOSS-Community/quickfire) 15 | 16 | --- 17 | 18 | Quickfire is a powerful Dart CLI tool that supercharges your Flutter project setup. With just a few commands, you can create a new Flutter project with all the essential features and configurations, allowing you to focus on building your app. 19 | 20 | ### Documentation 21 | 22 | [See our documentation for better understanding](https://utkarshs-organization-1.gitbook.io/quickfire/) 23 | 24 | [Quickfire website](https://quickfire.framer.website/) 25 | 26 | # Pre-requisites 27 | 28 | - `flutter sdk` 29 | 30 | ### Installing `quickfire` globally : 31 | 32 | - `dart pub global activate quickfire` 33 | 34 | ### List of commands : 35 | 36 | - `quickfire create` Create a new `flutter project` 37 | - `quckfire publish` Make your flutter app publish ready on Play Store. 38 | - `quickfire build` Build appbundle with incremented version code. 39 | -------------------------------------------------------------------------------- /lib/core/create_project/stripe_handler.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | class StripeHandler { 4 | static Future implementStripe( 5 | {required String projectName, required String orgName}) async { 6 | final File buildGradleFile = File('android/app/build.gradle'); 7 | final File mainActivityFile = File( 8 | 'android/app/src/main/kotlin/com/$orgName/$projectName/MainActivity.kt'); 9 | final ProcessResult addStripeResult = await Process.run( 10 | 'dart', 11 | ['pub', 'add', 'flutter_stripe'], 12 | ); 13 | if (addStripeResult.exitCode != 0) { 14 | print( 15 | 'Error adding Stripe packages. Check your internet connection and try again.'); 16 | print(addStripeResult.stderr); 17 | return; 18 | } 19 | buildGradleFile.writeAsStringSync(''' 20 | plugins { 21 | id "com.android.application" 22 | id "kotlin-android" 23 | id "dev.flutter.flutter-gradle-plugin" 24 | } 25 | 26 | def localProperties = new Properties() 27 | def localPropertiesFile = rootProject.file('local.properties') 28 | if (localPropertiesFile.exists()) { 29 | localPropertiesFile.withReader('UTF-8') { reader -> 30 | localProperties.load(reader) 31 | } 32 | } 33 | 34 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 35 | if (flutterVersionCode == null) { 36 | flutterVersionCode = '1' 37 | } 38 | 39 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 40 | if (flutterVersionName == null) { 41 | flutterVersionName = '1.0' 42 | } 43 | 44 | android { 45 | namespace "com.$orgName.$projectName" 46 | compileSdkVersion flutter.compileSdkVersion 47 | ndkVersion flutter.ndkVersion 48 | 49 | compileOptions { 50 | sourceCompatibility JavaVersion.VERSION_1_8 51 | targetCompatibility JavaVersion.VERSION_1_8 52 | } 53 | 54 | kotlinOptions { 55 | jvmTarget = '1.8' 56 | } 57 | 58 | sourceSets { 59 | main.java.srcDirs += 'src/main/kotlin' 60 | } 61 | 62 | defaultConfig { 63 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 64 | applicationId "com.$orgName.$projectName" 65 | // You can update the following values to match your application needs. 66 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 67 | minSdkVersion 21 68 | targetSdkVersion flutter.targetSdkVersion 69 | versionCode flutterVersionCode.toInteger() 70 | versionName flutterVersionName 71 | } 72 | 73 | buildTypes { 74 | release { 75 | // TODO: Add your own signing config for the release build. 76 | // Signing with the debug keys for now, so `flutter run --release` works. 77 | signingConfig signingConfigs.debug 78 | } 79 | } 80 | } 81 | 82 | flutter { 83 | source '../..' 84 | } 85 | 86 | dependencies {} 87 | 88 | '''); 89 | 90 | mainActivityFile.writeAsStringSync(''' 91 | package com.$orgName.$projectName 92 | 93 | import io.flutter.embedding.android.FlutterActivity 94 | import io.flutter.embedding.android.FlutterFragmentActivity 95 | 96 | class MainActivity: FlutterFragmentActivity() { 97 | } 98 | '''); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/core/deploy_project/gradle_handler.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | class GradleHandler { 4 | static Future updateCompileSdkVersion() async { 5 | final File buildGradle = File('android/app/build.gradle'); 6 | String content = buildGradle.readAsStringSync(); 7 | 8 | String oldValue = 'compileSdkVersion flutter.compileSdkVersion'; 9 | String newValue = 'compileSdkVersion 34'; 10 | 11 | content = content.replaceAllMapped( 12 | RegExp(oldValue), 13 | (match) => newValue, 14 | ); 15 | buildGradle.writeAsStringSync(content); 16 | } 17 | 18 | static Future referenceKeyStoreInGradle() async { 19 | final File buildGradle = File('android/app/build.gradle'); 20 | String keystorePropertiesSnippet = ''' 21 | def keystoreProperties = new Properties() 22 | def keystorePropertiesFile = rootProject.file('key.properties') 23 | if (keystorePropertiesFile.exists()) { 24 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 25 | } 26 | '''; 27 | 28 | try { 29 | String contents = await buildGradle.readAsString(); 30 | 31 | // Find the position to insert the keystorePropertiesSnippet before 'android' block 32 | int androidBlockIndex = contents.indexOf('android {'); 33 | if (androidBlockIndex != -1) { 34 | String updatedContents = contents.replaceRange( 35 | androidBlockIndex, androidBlockIndex, keystorePropertiesSnippet); 36 | 37 | // Write the updated contents back to the file 38 | await buildGradle.writeAsString(updatedContents); 39 | print('Build.gradle file updated successfully.'); 40 | } else { 41 | print('Error: Could not find the "android {" block in build.gradle.'); 42 | } 43 | } catch (e) { 44 | print('Error reading/writing build.gradle file: $e'); 45 | } 46 | } 47 | 48 | static Future updateBuildGradle() async { 49 | File buildGradleFile = File( 50 | 'android/app/build.gradle'); // Update the path to your build.gradle file 51 | String keystorePropertiesSnippet = ''' 52 | signingConfigs { 53 | release { 54 | keyAlias keystoreProperties['keyAlias'] 55 | keyPassword keystoreProperties['keyPassword'] 56 | storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null 57 | storePassword keystoreProperties['storePassword'] 58 | } 59 | } 60 | buildTypes { 61 | release { 62 | signingConfig signingConfigs.release 63 | } 64 | 65 | 66 | '''; 67 | 68 | try { 69 | String contents = await buildGradleFile.readAsString(); 70 | 71 | // Find the position to replace the existing 'release' block in buildTypes 72 | int releaseBlockStart = contents.indexOf('buildTypes {'); 73 | int releaseBlockEnd = contents.indexOf('}', releaseBlockStart); 74 | 75 | if (releaseBlockStart != -1 && releaseBlockEnd != -1) { 76 | String existingReleaseBlock = 77 | contents.substring(releaseBlockStart, releaseBlockEnd + 1); 78 | String updatedContents = contents.replaceFirst( 79 | existingReleaseBlock, keystorePropertiesSnippet); 80 | 81 | // Write the updated contents back to the file 82 | await buildGradleFile.writeAsString(updatedContents); 83 | print('Build.gradle file updated successfully.'); 84 | } else { 85 | print( 86 | 'Error: Could not find the "buildTypes {" block in build.gradle.'); 87 | } 88 | } catch (e) { 89 | print('Error reading/writing build.gradle file: $e'); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/core/create_project/notification_handler.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | class NotificationHandler { 4 | static Future implementFirebaseNotification(String projectName) async { 5 | // add firebase messaging and local notification packages 6 | final ProcessResult addFirebaseMessaging = await Process.run( 7 | 'dart', 8 | ['pub', 'add', 'firebase_messaging'], 9 | ); 10 | final ProcessResult addFlutterLocalNoti = await Process.run( 11 | 'dart', 12 | ['pub', 'add', 'flutter_local_notifications'], 13 | ); 14 | 15 | await Process.run( 16 | 'dart', 17 | ['pub', 'add', 'http'], 18 | ); 19 | 20 | if (addFirebaseMessaging.exitCode != 0) { 21 | print( 22 | 'Error adding firebase_messaging packages. Check your internet connection and try again.'); 23 | print(addFirebaseMessaging.stderr); 24 | return; 25 | } 26 | 27 | if (addFlutterLocalNoti.exitCode != 0) { 28 | print( 29 | 'Error adding flutter_local_notifications package. Check your internet connection and try again.'); 30 | print(addFlutterLocalNoti.stderr); 31 | return; 32 | } 33 | 34 | print('added notification packages to project'); 35 | 36 | // create notification feature 37 | final Directory notificationFeature = 38 | Directory('lib/features/notification'); 39 | notificationFeature.createSync(); 40 | final Directory notificationServiceFolder = 41 | Directory('lib/features/notification/services'); 42 | notificationServiceFolder.createSync(); 43 | final File notificationService = 44 | File('lib/features/notification/services/notification_services.dart'); 45 | if (!notificationService.existsSync()) { 46 | notificationService.createSync(); 47 | notificationService.writeAsStringSync(''' 48 | import 'dart:convert'; 49 | 50 | import 'package:firebase_messaging/firebase_messaging.dart'; 51 | import 'package:flutter_local_notifications/flutter_local_notifications.dart'; 52 | import 'package:$projectName/main.dart'; 53 | import 'package:http/http.dart' as http; 54 | 55 | class NotificationService { 56 | 57 | // Api function to send push messages via the app itself 58 | static sendPushMessage( 59 | {required String token, 60 | required String body, 61 | required String title}) async { 62 | try { 63 | await http.post(Uri.parse('https://fcm.googleapis.com/fcm/send'), 64 | headers: { 65 | 'Content-Type': 'application/json', 66 | 'Authorization': 67 | 'key=' 68 | }, 69 | body: jsonEncode({ 70 | 'priority': 'high', 71 | 'data': { 72 | 'click_action': 'FLUTTER_NOTIFICATION_CLICK', 73 | 'status': 'done', 74 | 'body': body, 75 | 'title': title, 76 | }, 77 | "notification": { 78 | "title": title, 79 | "body": body, 80 | "android_channel_id": "" 81 | }, 82 | "to": token 83 | })); 84 | } catch (e) { 85 | print(e.toString()); 86 | } 87 | } 88 | 89 | static Future initLocalNotifications() async { 90 | const AndroidInitializationSettings initializationSettingsAndroid = 91 | AndroidInitializationSettings('@mipmap/ic_launcher'); 92 | final InitializationSettings initializationSettings = 93 | InitializationSettings(android: initializationSettingsAndroid); 94 | await flutterLocalNotificationsPlugin.initialize(initializationSettings); 95 | } 96 | 97 | static Future showLocalNotification(RemoteMessage message) async { 98 | const AndroidNotificationDetails androidPlatformChannelSpecifics = 99 | AndroidNotificationDetails( 100 | '', // Change this to a unique channel ID 101 | 'Your Channel Name', // no need to change this 102 | channelDescription: '', 103 | importance: Importance.max, 104 | priority: Priority.high, 105 | ); 106 | const NotificationDetails platformChannelSpecifics = 107 | NotificationDetails(android: androidPlatformChannelSpecifics); 108 | 109 | await flutterLocalNotificationsPlugin.show( 110 | 0, 111 | message.data['title'], 112 | message.data['body'], 113 | platformChannelSpecifics, 114 | payload: 'item x', 115 | ); 116 | } 117 | } 118 | 119 | '''); 120 | } 121 | 122 | // change androidmanifest.xml 123 | final File manifestFile = File('android/app/src/main/AndroidManifest.xml'); 124 | manifestFile.writeAsStringSync(''' 125 | 126 | 127 | 131 | 139 | 143 | 147 | 150 | /> 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 165 | 168 | 169 | 170 | 171 | '''); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /lib/commands/create_project.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: unused_local_variable 2 | 3 | import 'dart:io'; 4 | 5 | import 'package:args/command_runner.dart'; 6 | import 'package:quickfire/core/create_project/assets_handler.dart'; 7 | import 'package:quickfire/core/deploy_project/gradle_handler.dart'; 8 | import 'package:quickfire/tools/choice_selctor.dart'; 9 | import 'package:quickfire/tools/cli_handler.dart'; 10 | import 'package:quickfire/core/create_project/auth_handler.dart'; 11 | import 'package:quickfire/core/create_project/command_handler.dart'; 12 | import 'package:quickfire/core/create_project/main_handler.dart'; 13 | import 'package:quickfire/core/create_project/notification_handler.dart'; 14 | import 'package:quickfire/core/create_project/on_boarding_creation.dart'; 15 | import 'package:quickfire/core/create_project/stripe_handler.dart'; 16 | 17 | class CreateProject extends Command { 18 | // Command details 19 | @override 20 | String get name => 'create'; 21 | 22 | @override 23 | String get description => 'Create a new Flutter project.'; 24 | 25 | CreateProject() { 26 | argParser.addFlag('verbose', 27 | abbr: 'v', help: 'Prints this usage information.', negatable: false); 28 | } 29 | 30 | // Handle Method 31 | @override 32 | Future run() async { 33 | // Create an instance of Cli handler 34 | final cliHandler = CliHandler(); 35 | String authOption = ''; 36 | late bool stripeOption; 37 | late bool onBoardingOption; 38 | cliHandler.printBoltCyanText('Enter your organization name: '); 39 | final String orgName = stdin.readLineSync() ?? ''; 40 | cliHandler.printBoltCyanText('Enter the name of the project: '); 41 | final String projectName = stdin.readLineSync() ?? ''; 42 | 43 | // Clears the screen 44 | cliHandler.clearScreen(); 45 | 46 | // using bold cyan and red color... 47 | cliHandler.printWithLoadingAmimation( 48 | '\x1B[1;36mquickfire\x1B[0m is creating \x1B[32m$projectName\x1B[0m'); 49 | 50 | if (projectName.isEmpty) { 51 | cliHandler.stopLoadingAnimation(); 52 | cliHandler.clearScreen(); 53 | cliHandler.printErrorText('Project name cannot be empty'); 54 | return; 55 | } 56 | if (orgName.isEmpty) { 57 | cliHandler.stopLoadingAnimation(); 58 | cliHandler.clearScreen(); 59 | cliHandler.printErrorText('Org name cannot be empty'); 60 | return; 61 | } 62 | 63 | final ProcessResult result = await Process.run( 64 | 'flutter', 65 | ['create', '--org', 'com.$orgName', projectName], 66 | ); 67 | 68 | if (result.exitCode == 0) { 69 | cliHandler.clearScreen(); 70 | print( 71 | '\x1B[1;32mFlutter project \x1B[1;36m$projectName \x1B[1;32mcreated successfully\x1B[0m'); 72 | } else { 73 | print(result.exitCode); 74 | cliHandler.printErrorText( 75 | 'Error creating Flutter project. Check the Flutter installation and try again.'); 76 | print(result.stderr); 77 | } 78 | 79 | await CommandHandler.createFeatureFirstArchitecture(projectName); 80 | await CommandHandler.createConstants(projectName); 81 | 82 | // On Boarding Feature 83 | final onBoardingChoices = ['Yes', 'No']; 84 | final onBoardingChoiceSelector = ChoiceSelector(onBoardingChoices); 85 | cliHandler.clearScreen(); 86 | cliHandler.printBoltCyanText('Do you want an on-boarding feature?'); 87 | onBoardingChoiceSelector.printOptions(); 88 | onBoardingChoiceSelector.handleArrowKeys(); 89 | int onBoardingIndex = onBoardingChoiceSelector.selectedIndexForOptions; 90 | if (onBoardingIndex == 0) { 91 | onBoardingOption = true; 92 | } else if (onBoardingIndex == 1) { 93 | onBoardingOption = false; 94 | } 95 | 96 | cliHandler.clearScreen(); 97 | final authChoices = ['firebase', 'appwrite', 'noAuth']; 98 | final authChoiceSelector = ChoiceSelector(authChoices); 99 | cliHandler.clearScreen(); 100 | cliHandler.printBoltCyanText('Choose your Backend as a Service (BaaS)'); 101 | authChoiceSelector.printOptions(); 102 | authChoiceSelector.handleArrowKeys(); 103 | int authChoiceIndex = authChoiceSelector.selectedIndexForOptions; 104 | if (authChoiceIndex == 0) { 105 | cliHandler.printBoldGreenText( 106 | 'Implementing Firebase Auth and Login screen for $projectName '); 107 | await AuthHandler.implementFirebase(projectName); 108 | authOption = 'firebase'; 109 | if (onBoardingOption) { 110 | await MainFileHandler.createFirebaseMainWithOnBoarding(projectName); 111 | await OnBoarding.createOnBoardingWithAuth(projectName); 112 | } else if (!onBoardingOption) { 113 | await MainFileHandler.createFirebaseMainWithoutOnBoarding(projectName); 114 | } 115 | } else if (authChoiceIndex == 1) { 116 | cliHandler.printBoldGreenText( 117 | 'Implementing Appwrite Auth and Login screen for $projectName ....'); 118 | await AuthHandler.implementAppwrite(projectName); 119 | authOption = 'appwrite'; 120 | if (onBoardingOption) { 121 | await MainFileHandler.createAppwriteMainWithOnBoarding(projectName); 122 | await OnBoarding.createOnBoardingWithAuth(projectName); 123 | } else if (!onBoardingOption) { 124 | await MainFileHandler.createAppwriteMainWithoutOnBoarding(projectName); 125 | } 126 | } else if (authChoiceIndex == 2) { 127 | if (onBoardingOption) { 128 | cliHandler.printBoldGreenText( 129 | 'You have choosed no BaaS for your application...'); 130 | await MainFileHandler.createNoAuthMainFileWithOnBoarding(projectName); 131 | await OnBoarding.createOnBoardingWithoutAuth(projectName); 132 | } else if (onBoardingOption) { 133 | cliHandler.printBoldGreenText( 134 | 'You have choosed no BaaS for your application...'); 135 | await MainFileHandler.createNoAuthMainFileWithoutOnBoarding( 136 | projectName); 137 | } 138 | authOption = 'no'; 139 | } 140 | 141 | // Ask for FCM and local Notifications 142 | 143 | if (authOption == 'firebase') { 144 | cliHandler.clearScreen(); 145 | final notificationOptions = ['Yes', 'No']; 146 | final notificationChoiceSelector = ChoiceSelector(notificationOptions); 147 | cliHandler.clearScreen(); 148 | cliHandler.printBoltCyanText( 149 | 'Do you want to implement Firebase Cloud Messaging with local notifications?'); 150 | notificationChoiceSelector.printOptions(); 151 | notificationChoiceSelector.handleArrowKeys(); 152 | int notificationChoiceIndex = 153 | notificationChoiceSelector.selectedIndexForOptions; 154 | if (notificationChoiceIndex == 0) { 155 | await NotificationHandler.implementFirebaseNotification(projectName); 156 | if (onBoardingOption) { 157 | await MainFileHandler.createNotificationSystemMainWithOnboarding( 158 | projectName); 159 | } else if (onBoardingOption) { 160 | await MainFileHandler.createNotficationSystemMainWithoutOnboarding( 161 | projectName); 162 | } 163 | } 164 | } 165 | 166 | // Ask for Stripe Payment Integration... 167 | cliHandler.clearScreen(); 168 | final stripeChoices = ['Yes', 'No']; 169 | final stripeChoiceSelector = ChoiceSelector(stripeChoices); 170 | cliHandler.clearScreen(); 171 | cliHandler.printBoltCyanText('Do you want to integrate Stripe payment?'); 172 | stripeChoiceSelector.printOptions(); 173 | stripeChoiceSelector.handleArrowKeys(); 174 | int stripeChoiceIndex = stripeChoiceSelector.selectedIndexForOptions; 175 | if (stripeChoiceIndex == 0) { 176 | cliHandler 177 | .printBoldGreenText('Integrating Stripe into your $projectName..'); 178 | await StripeHandler.implementStripe( 179 | projectName: projectName, 180 | orgName: orgName, 181 | ); 182 | } 183 | 184 | await GradleHandler.updateCompileSdkVersion(); 185 | await AssetsHandler.handleAssets(); 186 | AssetsHandler.replaceAssetsBlock(); 187 | 188 | cliHandler.clearScreen(); 189 | 190 | cliHandler.printBoldGreenText('$projectName created by Quickfire.'); 191 | cliHandler.printBoltCyanText('\$cd $projectName'); 192 | 193 | cliHandler.stopLoadingAnimation(); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /lib/core/deploy_project/keystore_handler.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:quickfire/tools/cli_handler.dart'; 5 | 6 | class KeystoreHandler { 7 | static Future generateWndowsKeystore() async { 8 | final cliHandler = CliHandler(); 9 | cliHandler.printWithLoadingAmimation( 10 | '\x1B[1;36mCreating upload keystore \x1B[0m \n'); 11 | cliHandler.printBoldGreenText('Enter the name for .jks file : '); 12 | final String jksName = stdin.readLineSync() ?? ''; 13 | cliHandler.eraseLastLine(); 14 | cliHandler.eraseLastLine(); 15 | 16 | cliHandler 17 | .printBoldGreenText('Enter the alias name (usually it is "upload") : '); 18 | final String aliasName = stdin.readLineSync() ?? ''; 19 | cliHandler.eraseLastLine(); 20 | cliHandler.eraseLastLine(); 21 | 22 | // Construct the keytool command 23 | String command = 24 | '''keytool -genkey -v -keystore %userprofile%\${$jksName}-keystore.jks ^-storetype JKS -keyalg RSA -keysize 2048 -validity 10000 ^-alias $aliasName'''; 25 | 26 | // Run the keytool command using Process.start 27 | Process process = await Process.start( 28 | '/bin/sh', 29 | ['-c', command], 30 | ); 31 | 32 | // Hook up stdout and stderr to capture the output 33 | StringBuffer outputBuffer = StringBuffer(); 34 | StringBuffer errorBuffer = StringBuffer(); 35 | 36 | process.stdout.transform(utf8.decoder).listen((String data) { 37 | outputBuffer.write(data); 38 | }); 39 | 40 | process.stderr.transform(utf8.decoder).listen((String data) { 41 | errorBuffer.write(data); 42 | }); 43 | 44 | // Provide the required input to the child process 45 | cliHandler.printBoltCyanText('Enter your keystore password : '); 46 | final String keyPass = stdin.readLineSync() ?? ''; 47 | process.stdin.writeln(keyPass); 48 | cliHandler.eraseLastLine(); 49 | cliHandler.eraseLastLine(); 50 | 51 | cliHandler.printBoldGreenText('Re-enter your keystore password : '); 52 | final String confirmKeyPass = stdin.readLineSync() ?? ''; 53 | process.stdin.writeln(confirmKeyPass); 54 | cliHandler.eraseLastLine(); 55 | cliHandler.eraseLastLine(); 56 | 57 | cliHandler.printBoldGreenText('What is your first and last name ? : '); 58 | final String name = stdin.readLineSync() ?? ''; 59 | process.stdin.writeln(name); 60 | cliHandler.eraseLastLine(); 61 | cliHandler.eraseLastLine(); 62 | 63 | cliHandler.printBoldGreenText( 64 | 'What is the name of your organizational unit ? : '); 65 | final String orgUnit = stdin.readLineSync() ?? ''; 66 | process.stdin.writeln(orgUnit); 67 | cliHandler.eraseLastLine(); 68 | cliHandler.eraseLastLine(); 69 | 70 | cliHandler.printBoldGreenText('What is the name of your organization ? : '); 71 | final String orgName = stdin.readLineSync() ?? ''; 72 | process.stdin.writeln(orgName); 73 | cliHandler.eraseLastLine(); 74 | cliHandler.eraseLastLine(); 75 | 76 | cliHandler 77 | .printBoldGreenText('What is the name of your city or locality ? : '); 78 | final String cityName = stdin.readLineSync() ?? ''; 79 | process.stdin.writeln(cityName); 80 | cliHandler.eraseLastLine(); 81 | cliHandler.eraseLastLine(); 82 | 83 | cliHandler 84 | .printBoldGreenText('What is the name of your state or province ? : '); 85 | final String stateName = stdin.readLineSync() ?? ''; 86 | process.stdin.writeln(stateName); 87 | cliHandler.eraseLastLine(); 88 | cliHandler.eraseLastLine(); 89 | 90 | cliHandler.printBoldGreenText( 91 | 'What is the two-letter country code for this unit ? : '); 92 | final String countryCode = stdin.readLineSync() ?? ''; 93 | process.stdin.writeln(countryCode); 94 | cliHandler.eraseLastLine(); 95 | cliHandler.eraseLastLine(); 96 | 97 | process.stdin.writeln('y'); 98 | 99 | // Wait for the process to complete 100 | int exitCode = await process.exitCode; 101 | print('Command exited with code $exitCode'); 102 | print('Output:\n$outputBuffer'); 103 | print('Error:\n$errorBuffer'); 104 | } 105 | 106 | static Future generateLinuxMacUploadKeystore() async { 107 | final cliHandler = CliHandler(); 108 | cliHandler.printWithLoadingAmimation( 109 | '\x1B[1;36mCreating upload keystore \x1B[0m \n'); 110 | cliHandler.printBoldGreenText('Enter the name for .jks file : '); 111 | final String jksName = stdin.readLineSync() ?? ''; 112 | cliHandler.eraseLastLine(); 113 | cliHandler.eraseLastLine(); 114 | 115 | cliHandler 116 | .printBoldGreenText('Enter the alias name (usually it is "upload") : '); 117 | final String aliasName = stdin.readLineSync() ?? ''; 118 | cliHandler.eraseLastLine(); 119 | cliHandler.eraseLastLine(); 120 | 121 | // Construct the keytool command 122 | String command = 123 | 'keytool -genkey -v -keystore ~/$jksName-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias $aliasName'; 124 | 125 | // Run the keytool command using Process.start 126 | Process process = await Process.start( 127 | '/bin/sh', 128 | ['-c', command], 129 | ); 130 | 131 | // Hook up stdout and stderr to capture the output 132 | StringBuffer outputBuffer = StringBuffer(); 133 | StringBuffer errorBuffer = StringBuffer(); 134 | 135 | process.stdout.transform(utf8.decoder).listen((String data) { 136 | outputBuffer.write(data); 137 | }); 138 | 139 | process.stderr.transform(utf8.decoder).listen((String data) { 140 | errorBuffer.write(data); 141 | }); 142 | 143 | // Provide the required input to the child process 144 | cliHandler.printBoltCyanText('Enter your keystore password : '); 145 | final String keyPass = stdin.readLineSync() ?? ''; 146 | process.stdin.writeln(keyPass); 147 | cliHandler.eraseLastLine(); 148 | cliHandler.eraseLastLine(); 149 | 150 | cliHandler.printBoldGreenText('Re-enter your keystore password : '); 151 | final String confirmKeyPass = stdin.readLineSync() ?? ''; 152 | process.stdin.writeln(confirmKeyPass); 153 | cliHandler.eraseLastLine(); 154 | cliHandler.eraseLastLine(); 155 | 156 | cliHandler.printBoldGreenText('What is your first and last name ? : '); 157 | final String name = stdin.readLineSync() ?? ''; 158 | process.stdin.writeln(name); 159 | cliHandler.eraseLastLine(); 160 | cliHandler.eraseLastLine(); 161 | 162 | cliHandler.printBoldGreenText( 163 | 'What is the name of your organizational unit ? : '); 164 | final String orgUnit = stdin.readLineSync() ?? ''; 165 | process.stdin.writeln(orgUnit); 166 | cliHandler.eraseLastLine(); 167 | cliHandler.eraseLastLine(); 168 | 169 | cliHandler.printBoldGreenText('What is the name of your organization ? : '); 170 | final String orgName = stdin.readLineSync() ?? ''; 171 | process.stdin.writeln(orgName); 172 | cliHandler.eraseLastLine(); 173 | cliHandler.eraseLastLine(); 174 | 175 | cliHandler 176 | .printBoldGreenText('What is the name of your city or locality ? : '); 177 | final String cityName = stdin.readLineSync() ?? ''; 178 | process.stdin.writeln(cityName); 179 | cliHandler.eraseLastLine(); 180 | cliHandler.eraseLastLine(); 181 | 182 | cliHandler 183 | .printBoldGreenText('What is the name of your state or province ? : '); 184 | final String stateName = stdin.readLineSync() ?? ''; 185 | process.stdin.writeln(stateName); 186 | cliHandler.eraseLastLine(); 187 | cliHandler.eraseLastLine(); 188 | 189 | cliHandler.printBoldGreenText( 190 | 'What is the two-letter country code for this unit ? : '); 191 | final String countryCode = stdin.readLineSync() ?? ''; 192 | process.stdin.writeln(countryCode); 193 | cliHandler.eraseLastLine(); 194 | cliHandler.eraseLastLine(); 195 | 196 | process.stdin.writeln('y'); 197 | 198 | // Wait for the process to complete 199 | int exitCode = await process.exitCode; 200 | print('Command exited with code $exitCode'); 201 | print('Output:\n$outputBuffer'); 202 | print('Error:\n$errorBuffer'); 203 | } 204 | 205 | static Future generateKeyProperties() async { 206 | final File keyPro = File('android/key.properties'); 207 | keyPro.createSync(); 208 | keyPro.writeAsStringSync(''' 209 | storePassword= 210 | keyPassword= 211 | keyAlias= 212 | storeFile= 213 | '''); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /lib/core/create_project/on_boarding_creation.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | class OnBoarding { 4 | static Future createOnBoardingWithAuth(String projectName) async { 5 | // Import shared pref and riverpod 6 | final ProcessResult addSharedPrefResult = await Process.run( 7 | 'dart', 8 | ['pub', 'add', 'shared_preferences'], 9 | ); 10 | 11 | final ProcessResult addRiverpodResult = await Process.run( 12 | 'dart', 13 | ['pub', 'add', 'flutter_riverpod'], 14 | ); 15 | 16 | if (addSharedPrefResult.exitCode != 0) { 17 | print( 18 | 'Error adding packages. Check your internet connection and try again.'); 19 | print(addSharedPrefResult.stderr); 20 | return; 21 | } 22 | if (addRiverpodResult.exitCode != 0) { 23 | print( 24 | 'Error adding packages. Check your internet connection and try again.'); 25 | print(addRiverpodResult.stderr); 26 | return; 27 | } 28 | 29 | print('added shared pref'); 30 | 31 | // create an on-boarding inside features folder 32 | final Directory onBoardingFolder = Directory('lib/features/onBoarding'); 33 | onBoardingFolder.createSync(); 34 | 35 | // go under onBoardingfolder and create an UI folder 36 | final Directory onBoardingUIFolder = 37 | Directory('lib/features/onBoarding/ui'); 38 | onBoardingUIFolder.createSync(); 39 | 40 | // create on_boarding_screen.dart 41 | final File onBoardinScreenFile = 42 | File('lib/features/onBoarding/ui/on_boarding_screen.dart'); 43 | if (!onBoardinScreenFile.existsSync()) { 44 | onBoardinScreenFile.createSync(); 45 | onBoardinScreenFile.writeAsStringSync(''' 46 | import 'package:$projectName/features/auth/ui/auth_screen.dart'; 47 | import 'package:$projectName/features/onBoarding/ui/intro_page2.dart'; 48 | import 'package:$projectName/features/onBoarding/ui/intro_page3.dart'; 49 | import 'package:$projectName/features/onBoarding/ui/intro_page1.dart'; 50 | import 'package:flutter/material.dart'; 51 | import 'package:shared_preferences/shared_preferences.dart'; 52 | 53 | class OnBoardingScreen extends StatefulWidget { 54 | const OnBoardingScreen({super.key}); 55 | 56 | @override 57 | State createState() => _OnBoardingScreenState(); 58 | } 59 | 60 | class _OnBoardingScreenState extends State { 61 | final PageController _controller = PageController(); 62 | 63 | bool onLastPage = false; 64 | 65 | void _completeOnboarding() async { 66 | SharedPreferences prefs = await SharedPreferences.getInstance(); 67 | prefs.setBool('hasSeenOnboarding', true); 68 | // ignore: use_build_context_synchronously 69 | Navigator.pushReplacement( 70 | context, 71 | MaterialPageRoute( 72 | builder: (context) => const AuthScreen())); // PageViewHome() 73 | } 74 | 75 | @override 76 | Widget build(BuildContext context) { 77 | return Scaffold( 78 | body: Stack( 79 | children: [ 80 | PageView( 81 | onPageChanged: (index) { 82 | setState(() { 83 | onLastPage = (index == 2); 84 | }); 85 | }, 86 | controller: _controller, 87 | children: const [ 88 | IntroPage1(), 89 | IntroPage2(), 90 | IntroPage3(), 91 | ], 92 | ), 93 | Container( 94 | alignment: const Alignment(0, 0.7), 95 | child: Column( 96 | mainAxisAlignment: MainAxisAlignment.end, 97 | children: [ 98 | if (onLastPage) 99 | Container( 100 | alignment: Alignment.centerRight, 101 | child: TextButton( 102 | onPressed: _completeOnboarding, 103 | child: const SizedBox( 104 | width: 216, 105 | child: Row( 106 | mainAxisAlignment: MainAxisAlignment.end, 107 | children: [ 108 | Text( 109 | 'Done', 110 | ), 111 | Icon( 112 | Icons.arrow_right, 113 | ) 114 | ], 115 | ), 116 | ), 117 | ), 118 | ), 119 | ], 120 | ), 121 | ), 122 | ], 123 | ), 124 | ); 125 | } 126 | } 127 | 128 | 129 | '''); 130 | } 131 | 132 | // create intro page 1 133 | final File introPage1 = File('lib/features/onBoarding/ui/intro_page1.dart'); 134 | if (!introPage1.existsSync()) { 135 | introPage1.createSync(); 136 | introPage1.writeAsStringSync(''' 137 | import 'package:flutter/material.dart'; 138 | class IntroPage1 extends StatelessWidget { 139 | const IntroPage1({super.key}); 140 | 141 | @override 142 | Widget build(BuildContext context) { 143 | return Scaffold( 144 | body: Center( 145 | child: Text('Intro Page 1'), 146 | ), 147 | ); 148 | } 149 | } 150 | '''); 151 | } 152 | 153 | // create intro page 2 154 | 155 | final File introPage2 = File('lib/features/onBoarding/ui/intro_page2.dart'); 156 | if (!introPage2.existsSync()) { 157 | introPage2.createSync(); 158 | introPage2.writeAsStringSync(''' 159 | import 'package:flutter/material.dart'; 160 | class IntroPage2 extends StatelessWidget { 161 | const IntroPage2({super.key}); 162 | 163 | @override 164 | Widget build(BuildContext context) { 165 | return Scaffold( 166 | body: Center( 167 | child: Text('Intro Page 2'), 168 | ), 169 | ); 170 | } 171 | } 172 | '''); 173 | } 174 | 175 | // create intro page 3 176 | final File introPage3 = File('lib/features/onBoarding/ui/intro_page3.dart'); 177 | if (!introPage3.existsSync()) { 178 | introPage3.createSync(); 179 | introPage3.writeAsStringSync(''' 180 | import 'package:flutter/material.dart'; 181 | class IntroPage3 extends StatelessWidget { 182 | const IntroPage3({super.key}); 183 | 184 | @override 185 | Widget build(BuildContext context) { 186 | return Scaffold( 187 | body: Center( 188 | child: Text('Intro Page 3'), 189 | ), 190 | ); 191 | } 192 | } 193 | '''); 194 | } 195 | } 196 | 197 | static Future createOnBoardingWithoutAuth(String projectName) async { 198 | // Import shared pref and riverpod 199 | final ProcessResult addSharedPrefResult = await Process.run( 200 | 'dart', 201 | ['pub', 'add', 'shared_preferences'], 202 | ); 203 | 204 | final ProcessResult addRiverpodResult = await Process.run( 205 | 'dart', 206 | ['pub', 'add', 'flutter_riverpod'], 207 | ); 208 | 209 | if (addSharedPrefResult.exitCode != 0) { 210 | print( 211 | 'Error adding packages. Check your internet connection and try again.'); 212 | print(addSharedPrefResult.stderr); 213 | return; 214 | } 215 | if (addRiverpodResult.exitCode != 0) { 216 | print( 217 | 'Error adding packages. Check your internet connection and try again.'); 218 | print(addRiverpodResult.stderr); 219 | return; 220 | } 221 | 222 | print('added shared pref'); 223 | 224 | // create an on-boarding inside features folder 225 | final Directory onBoardingFolder = Directory('lib/features/onBoarding'); 226 | onBoardingFolder.createSync(); 227 | 228 | // go under onBoardingfolder and create an UI folder 229 | final Directory onBoardingUIFolder = 230 | Directory('lib/features/onBoarding/ui'); 231 | onBoardingUIFolder.createSync(); 232 | 233 | // create on_boarding_screen.dart 234 | final File onBoardinScreenFile = 235 | File('lib/features/onBoarding/ui/on_boarding_screen.dart'); 236 | if (!onBoardinScreenFile.existsSync()) { 237 | onBoardinScreenFile.createSync(); 238 | onBoardinScreenFile.writeAsStringSync(''' 239 | import 'package:$projectName/shared/nav_bar.dart'; 240 | import 'package:$projectName/features/onBoarding/ui/intro_page2.dart'; 241 | import 'package:$projectName/features/onBoarding/ui/intro_page3.dart'; 242 | import 'package:$projectName/features/onBoarding/ui/intro_page1.dart'; 243 | import 'package:flutter/material.dart'; 244 | import 'package:shared_preferences/shared_preferences.dart'; 245 | 246 | class OnBoardingScreen extends StatefulWidget { 247 | const OnBoardingScreen({super.key}); 248 | 249 | @override 250 | State createState() => _OnBoardingScreenState(); 251 | } 252 | 253 | class _OnBoardingScreenState extends State { 254 | final PageController _controller = PageController(); 255 | 256 | bool onLastPage = false; 257 | 258 | void _completeOnboarding() async { 259 | SharedPreferences prefs = await SharedPreferences.getInstance(); 260 | prefs.setBool('hasSeenOnboarding', true); 261 | // ignore: use_build_context_synchronously 262 | Navigator.pushReplacement( 263 | context, 264 | MaterialPageRoute( 265 | builder: (context) => const NavigationScreen())); 266 | } 267 | 268 | @override 269 | Widget build(BuildContext context) { 270 | return Scaffold( 271 | body: Stack( 272 | children: [ 273 | PageView( 274 | onPageChanged: (index) { 275 | setState(() { 276 | onLastPage = (index == 2); 277 | }); 278 | }, 279 | controller: _controller, 280 | children: const [ 281 | IntroPage1(), 282 | IntroPage2(), 283 | IntroPage3(), 284 | ], 285 | ), 286 | Container( 287 | alignment: const Alignment(0, 0.7), 288 | child: Column( 289 | mainAxisAlignment: MainAxisAlignment.end, 290 | children: [ 291 | if (onLastPage) 292 | Container( 293 | alignment: Alignment.centerRight, 294 | child: TextButton( 295 | onPressed: _completeOnboarding, 296 | child: const SizedBox( 297 | width: 216, 298 | child: Row( 299 | mainAxisAlignment: MainAxisAlignment.end, 300 | children: [ 301 | Text( 302 | 'Done', 303 | ), 304 | Icon( 305 | Icons.arrow_right, 306 | ) 307 | ], 308 | ), 309 | ), 310 | ), 311 | ), 312 | ], 313 | ), 314 | ), 315 | ], 316 | ), 317 | ); 318 | } 319 | } 320 | 321 | 322 | '''); 323 | } 324 | 325 | // create intro page 1 326 | final File introPage1 = File('lib/features/onBoarding/ui/intro_page1.dart'); 327 | if (!introPage1.existsSync()) { 328 | introPage1.createSync(); 329 | introPage1.writeAsStringSync(''' 330 | import 'package:flutter/material.dart'; 331 | class IntroPage1 extends StatelessWidget { 332 | const IntroPage1({super.key}); 333 | 334 | @override 335 | Widget build(BuildContext context) { 336 | return Scaffold( 337 | body: Center( 338 | child: Text('Intro Page 1'), 339 | ), 340 | ); 341 | } 342 | } 343 | '''); 344 | } 345 | 346 | // create intro page 2 347 | 348 | final File introPage2 = File('lib/features/onBoarding/ui/intro_page2.dart'); 349 | if (!introPage2.existsSync()) { 350 | introPage2.createSync(); 351 | introPage2.writeAsStringSync(''' 352 | import 'package:flutter/material.dart'; 353 | class IntroPage2 extends StatelessWidget { 354 | const IntroPage2({super.key}); 355 | 356 | @override 357 | Widget build(BuildContext context) { 358 | return Scaffold( 359 | body: Center( 360 | child: Text('Intro Page 2'), 361 | ), 362 | ); 363 | } 364 | } 365 | '''); 366 | } 367 | 368 | // create intro page 3 369 | final File introPage3 = File('lib/features/onBoarding/ui/intro_page3.dart'); 370 | if (!introPage3.existsSync()) { 371 | introPage3.createSync(); 372 | introPage3.writeAsStringSync(''' 373 | import 'package:flutter/material.dart'; 374 | class IntroPage3 extends StatelessWidget { 375 | const IntroPage3({super.key}); 376 | 377 | @override 378 | Widget build(BuildContext context) { 379 | return Scaffold( 380 | body: Center( 381 | child: Text('Intro Page 3'), 382 | ), 383 | ); 384 | } 385 | } 386 | '''); 387 | } 388 | } 389 | } 390 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | sha256: "36a321c3d2cbe01cbcb3540a87b8843846e0206df3e691fa7b23e19e78de6d49" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "65.0.0" 12 | analyzer: 13 | dependency: transitive 14 | description: 15 | name: analyzer 16 | sha256: dfe03b90ec022450e22513b5e5ca1f01c0c01de9c3fba2f7fd233cb57a6b9a07 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "6.3.0" 20 | args: 21 | dependency: "direct main" 22 | description: 23 | name: args 24 | sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "2.4.2" 28 | async: 29 | dependency: transitive 30 | description: 31 | name: async 32 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "2.11.0" 36 | boolean_selector: 37 | dependency: transitive 38 | description: 39 | name: boolean_selector 40 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "2.1.1" 44 | collection: 45 | dependency: transitive 46 | description: 47 | name: collection 48 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.18.0" 52 | convert: 53 | dependency: transitive 54 | description: 55 | name: convert 56 | sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "3.1.1" 60 | coverage: 61 | dependency: transitive 62 | description: 63 | name: coverage 64 | sha256: ac86d3abab0f165e4b8f561280ff4e066bceaac83c424dd19f1ae2c2fcd12ca9 65 | url: "https://pub.dev" 66 | source: hosted 67 | version: "1.7.1" 68 | crypto: 69 | dependency: transitive 70 | description: 71 | name: crypto 72 | sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab 73 | url: "https://pub.dev" 74 | source: hosted 75 | version: "3.0.3" 76 | file: 77 | dependency: transitive 78 | description: 79 | name: file 80 | sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" 81 | url: "https://pub.dev" 82 | source: hosted 83 | version: "7.0.0" 84 | frontend_server_client: 85 | dependency: transitive 86 | description: 87 | name: frontend_server_client 88 | sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" 89 | url: "https://pub.dev" 90 | source: hosted 91 | version: "3.2.0" 92 | glob: 93 | dependency: transitive 94 | description: 95 | name: glob 96 | sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" 97 | url: "https://pub.dev" 98 | source: hosted 99 | version: "2.1.2" 100 | http: 101 | dependency: "direct main" 102 | description: 103 | name: http 104 | sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139 105 | url: "https://pub.dev" 106 | source: hosted 107 | version: "1.1.2" 108 | http_multi_server: 109 | dependency: transitive 110 | description: 111 | name: http_multi_server 112 | sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" 113 | url: "https://pub.dev" 114 | source: hosted 115 | version: "3.2.1" 116 | http_parser: 117 | dependency: transitive 118 | description: 119 | name: http_parser 120 | sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" 121 | url: "https://pub.dev" 122 | source: hosted 123 | version: "4.0.2" 124 | io: 125 | dependency: transitive 126 | description: 127 | name: io 128 | sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" 129 | url: "https://pub.dev" 130 | source: hosted 131 | version: "1.0.4" 132 | js: 133 | dependency: transitive 134 | description: 135 | name: js 136 | sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 137 | url: "https://pub.dev" 138 | source: hosted 139 | version: "0.6.7" 140 | lints: 141 | dependency: "direct dev" 142 | description: 143 | name: lints 144 | sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" 145 | url: "https://pub.dev" 146 | source: hosted 147 | version: "2.1.1" 148 | logging: 149 | dependency: transitive 150 | description: 151 | name: logging 152 | sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" 153 | url: "https://pub.dev" 154 | source: hosted 155 | version: "1.2.0" 156 | matcher: 157 | dependency: transitive 158 | description: 159 | name: matcher 160 | sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" 161 | url: "https://pub.dev" 162 | source: hosted 163 | version: "0.12.16" 164 | meta: 165 | dependency: transitive 166 | description: 167 | name: meta 168 | sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 169 | url: "https://pub.dev" 170 | source: hosted 171 | version: "1.11.0" 172 | mime: 173 | dependency: transitive 174 | description: 175 | name: mime 176 | sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e 177 | url: "https://pub.dev" 178 | source: hosted 179 | version: "1.0.4" 180 | node_preamble: 181 | dependency: transitive 182 | description: 183 | name: node_preamble 184 | sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" 185 | url: "https://pub.dev" 186 | source: hosted 187 | version: "2.0.2" 188 | package_config: 189 | dependency: transitive 190 | description: 191 | name: package_config 192 | sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" 193 | url: "https://pub.dev" 194 | source: hosted 195 | version: "2.1.0" 196 | path: 197 | dependency: "direct main" 198 | description: 199 | name: path 200 | sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" 201 | url: "https://pub.dev" 202 | source: hosted 203 | version: "1.8.3" 204 | pool: 205 | dependency: transitive 206 | description: 207 | name: pool 208 | sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" 209 | url: "https://pub.dev" 210 | source: hosted 211 | version: "1.5.1" 212 | pub_semver: 213 | dependency: transitive 214 | description: 215 | name: pub_semver 216 | sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" 217 | url: "https://pub.dev" 218 | source: hosted 219 | version: "2.1.4" 220 | shelf: 221 | dependency: transitive 222 | description: 223 | name: shelf 224 | sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 225 | url: "https://pub.dev" 226 | source: hosted 227 | version: "1.4.1" 228 | shelf_packages_handler: 229 | dependency: transitive 230 | description: 231 | name: shelf_packages_handler 232 | sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" 233 | url: "https://pub.dev" 234 | source: hosted 235 | version: "3.0.2" 236 | shelf_static: 237 | dependency: transitive 238 | description: 239 | name: shelf_static 240 | sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e 241 | url: "https://pub.dev" 242 | source: hosted 243 | version: "1.1.2" 244 | shelf_web_socket: 245 | dependency: transitive 246 | description: 247 | name: shelf_web_socket 248 | sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" 249 | url: "https://pub.dev" 250 | source: hosted 251 | version: "1.0.4" 252 | source_map_stack_trace: 253 | dependency: transitive 254 | description: 255 | name: source_map_stack_trace 256 | sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" 257 | url: "https://pub.dev" 258 | source: hosted 259 | version: "2.1.1" 260 | source_maps: 261 | dependency: transitive 262 | description: 263 | name: source_maps 264 | sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" 265 | url: "https://pub.dev" 266 | source: hosted 267 | version: "0.10.12" 268 | source_span: 269 | dependency: transitive 270 | description: 271 | name: source_span 272 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 273 | url: "https://pub.dev" 274 | source: hosted 275 | version: "1.10.0" 276 | stack_trace: 277 | dependency: transitive 278 | description: 279 | name: stack_trace 280 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" 281 | url: "https://pub.dev" 282 | source: hosted 283 | version: "1.11.1" 284 | stream_channel: 285 | dependency: transitive 286 | description: 287 | name: stream_channel 288 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 289 | url: "https://pub.dev" 290 | source: hosted 291 | version: "2.1.2" 292 | string_scanner: 293 | dependency: transitive 294 | description: 295 | name: string_scanner 296 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 297 | url: "https://pub.dev" 298 | source: hosted 299 | version: "1.2.0" 300 | term_glyph: 301 | dependency: transitive 302 | description: 303 | name: term_glyph 304 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 305 | url: "https://pub.dev" 306 | source: hosted 307 | version: "1.2.1" 308 | test: 309 | dependency: "direct dev" 310 | description: 311 | name: test 312 | sha256: a1f7595805820fcc05e5c52e3a231aedd0b72972cb333e8c738a8b1239448b6f 313 | url: "https://pub.dev" 314 | source: hosted 315 | version: "1.24.9" 316 | test_api: 317 | dependency: transitive 318 | description: 319 | name: test_api 320 | sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" 321 | url: "https://pub.dev" 322 | source: hosted 323 | version: "0.6.1" 324 | test_core: 325 | dependency: transitive 326 | description: 327 | name: test_core 328 | sha256: a757b14fc47507060a162cc2530d9a4a2f92f5100a952c7443b5cad5ef5b106a 329 | url: "https://pub.dev" 330 | source: hosted 331 | version: "0.5.9" 332 | typed_data: 333 | dependency: transitive 334 | description: 335 | name: typed_data 336 | sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c 337 | url: "https://pub.dev" 338 | source: hosted 339 | version: "1.3.2" 340 | vm_service: 341 | dependency: transitive 342 | description: 343 | name: vm_service 344 | sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 345 | url: "https://pub.dev" 346 | source: hosted 347 | version: "13.0.0" 348 | watcher: 349 | dependency: transitive 350 | description: 351 | name: watcher 352 | sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" 353 | url: "https://pub.dev" 354 | source: hosted 355 | version: "1.1.0" 356 | web: 357 | dependency: transitive 358 | description: 359 | name: web 360 | sha256: edc8a9573dd8c5a83a183dae1af2b6fd4131377404706ca4e5420474784906fa 361 | url: "https://pub.dev" 362 | source: hosted 363 | version: "0.4.0" 364 | web_socket_channel: 365 | dependency: transitive 366 | description: 367 | name: web_socket_channel 368 | sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b 369 | url: "https://pub.dev" 370 | source: hosted 371 | version: "2.4.0" 372 | webkit_inspection_protocol: 373 | dependency: transitive 374 | description: 375 | name: webkit_inspection_protocol 376 | sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" 377 | url: "https://pub.dev" 378 | source: hosted 379 | version: "1.2.1" 380 | yaml: 381 | dependency: "direct main" 382 | description: 383 | name: yaml 384 | sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" 385 | url: "https://pub.dev" 386 | source: hosted 387 | version: "3.1.2" 388 | yaml_edit: 389 | dependency: "direct main" 390 | description: 391 | name: yaml_edit 392 | sha256: "1579d4a0340a83cf9e4d580ea51a16329c916973bffd5bd4b45e911b25d46bfd" 393 | url: "https://pub.dev" 394 | source: hosted 395 | version: "2.1.1" 396 | sdks: 397 | dart: ">=3.2.0 <4.0.0" 398 | -------------------------------------------------------------------------------- /lib/core/create_project/command_handler.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:quickfire/tools/cli_handler.dart'; 4 | 5 | class CommandHandler { 6 | static Future createFeatureFirstArchitecture(String projectName) async { 7 | final cliHandler = CliHandler(); 8 | int numOfFeatures; 9 | // move to the newly created project directory 10 | final Directory projectDirectory = Directory(projectName); 11 | if (!projectDirectory.existsSync()) { 12 | cliHandler.printErrorText('Error: Project directory does not exist.'); 13 | return; 14 | } 15 | 16 | Directory.current = projectDirectory.path; 17 | // Ask about number of features from user 18 | cliHandler.printBoltCyanText( 19 | 'Enter the number of features required in $projectName: (int)'); 20 | 21 | final String numOfFeaturesString = stdin.readLineSync() ?? ''; 22 | cliHandler.eraseLastLine(); 23 | numOfFeatures = int.parse(numOfFeaturesString); 24 | List featuresArray = []; 25 | 26 | for (int i = 1; i <= numOfFeatures;) { 27 | cliHandler.printBoldGreenText('Enter the name of feature $i : '); 28 | String nameOfFeature = stdin.readLineSync() ?? ''; 29 | cliHandler.eraseLastLine(); 30 | cliHandler.eraseLastLine(); 31 | featuresArray.add(nameOfFeature); 32 | i++; 33 | } 34 | 35 | cliHandler.clearScreen(); 36 | 37 | cliHandler.printBoltCyanText('All of your features are : '); 38 | print(featuresArray); 39 | 40 | // create 'features' folder inside 'projectName/lib' 41 | final Directory featuresFolder = Directory('lib/features'); 42 | featuresFolder.createSync(recursive: true); 43 | 44 | // create a folder for each feature 45 | for (String feature in featuresArray) { 46 | final Directory featuresFolder = Directory('lib/features/$feature'); 47 | featuresFolder.createSync(); 48 | // Under each feature folder create subfolders for bloc,ui, widgets and repo 49 | final Directory blocFolder = Directory('lib/features/$feature/bloc'); 50 | final Directory uiFolder = Directory('lib/features/$feature/ui'); 51 | final Directory repoFolder = Directory('lib/features/$feature/repo'); 52 | final Directory widgetsFolder = 53 | Directory('lib/features/$feature/widgets'); 54 | blocFolder.createSync(); 55 | uiFolder.createSync(); 56 | repoFolder.createSync(); 57 | widgetsFolder.createSync(); 58 | } 59 | 60 | // Creating UI files 61 | for (String feature in featuresArray) { 62 | String featureName = feature[0].toUpperCase() + feature.substring(1); 63 | 64 | final Directory uiFolder = Directory('lib/features/$feature/ui'); 65 | 66 | if (uiFolder.existsSync()) { 67 | final File screenFile = 68 | File('lib/features/$feature/ui/${feature}_screen.dart'); 69 | 70 | if (!screenFile.existsSync()) { 71 | // create a new dart file 72 | screenFile.createSync(); 73 | 74 | // write the basic content to the dart file. 75 | screenFile.writeAsStringSync(''' 76 | import 'package:flutter/material.dart'; 77 | 78 | class ${featureName}Screen extends StatelessWidget { 79 | const ${featureName}Screen({super.key}); 80 | 81 | @override 82 | Widget build(BuildContext context) { 83 | return const Scaffold( 84 | body: Center( 85 | child: Text('${featureName}Screen '), 86 | ), 87 | ); 88 | } 89 | } 90 | 91 | '''); 92 | } 93 | } 94 | } 95 | 96 | // create navigation bar..... 97 | 98 | // create shared folder 99 | final sharedFolder = Directory('lib/shared'); 100 | sharedFolder.createSync(); 101 | 102 | final File navbar = File('lib/shared/nav_bar.dart'); 103 | if (!navbar.existsSync()) { 104 | navbar.createSync(); 105 | navbar.writeAsStringSync(''' 106 | import 'package:flutter/material.dart'; 107 | ''', mode: FileMode.append); 108 | 109 | for (var feature in featuresArray) { 110 | navbar.writeAsStringSync( 111 | "import 'package:$projectName/features/$feature/ui/${feature}_screen.dart';\n", 112 | mode: FileMode.append, 113 | ); 114 | } 115 | navbar.writeAsStringSync(''' 116 | class NavigationScreen extends StatefulWidget { 117 | const NavigationScreen({super.key}); 118 | 119 | @override 120 | State createState() => _NavigationScreenState(); 121 | } 122 | 123 | class _NavigationScreenState extends State { 124 | int _selectedTab = 0; 125 | 126 | _changeTab(int index) { 127 | setState(() { 128 | _selectedTab = index; 129 | }); 130 | } 131 | 132 | List _pages = [ 133 | 134 | ''', mode: FileMode.append); 135 | } 136 | 137 | // create custom_button.dart inside shared folder 138 | 139 | final File customButtonFile = File('lib/shared/custom_button.dart'); 140 | if (!customButtonFile.existsSync()) { 141 | customButtonFile.createSync(); 142 | customButtonFile.writeAsStringSync(''' 143 | 144 | import 'package:flutter/material.dart'; 145 | import 'package:$projectName/constants/dimensions.dart'; 146 | 147 | class CustomButton extends StatefulWidget { 148 | final String text; 149 | final VoidCallback function; 150 | final bool isAsync; 151 | const CustomButton({ 152 | this.isAsync = false, 153 | required this.text, 154 | required this.function, 155 | super.key, 156 | }); 157 | 158 | @override 159 | State createState() => _CustomButtonState(); 160 | } 161 | 162 | class _CustomButtonState extends State { 163 | bool isTapped = false; 164 | @override 165 | Widget build(BuildContext context) { 166 | return GestureDetector( 167 | onTapDown: (_) { 168 | setState(() { 169 | isTapped = true; 170 | }); 171 | }, 172 | onTapUp: (_) { 173 | setState(() { 174 | isTapped = false; 175 | }); 176 | widget.function(); 177 | }, 178 | onTapCancel: () { 179 | setState(() { 180 | isTapped = false; 181 | }); 182 | }, 183 | child: AnimatedContainer( 184 | width: getScreenWidth(context), 185 | margin: EdgeInsets.symmetric( 186 | horizontal: getScreenWidth(context) * (isTapped ? 0.1 : 0.05), 187 | vertical: getScreenWidth(context) * 0.02, 188 | ), 189 | duration: const Duration(milliseconds: 100), 190 | decoration: BoxDecoration( 191 | color: Colors.black, borderRadius: BorderRadius.circular(15)), 192 | padding: EdgeInsets.symmetric( 193 | vertical: getScreenWidth(context) * 0.035, 194 | ), 195 | child: Center( 196 | child: Text( 197 | widget.text, 198 | style: const TextStyle( 199 | color: Colors.white, 200 | fontWeight: FontWeight.bold, 201 | ), 202 | ), 203 | ), 204 | ), 205 | ); 206 | } 207 | } 208 | 209 | 210 | '''); 211 | } 212 | 213 | // create custom text field 214 | final customTextField = File('lib/shared/custom_text_field.dart'); 215 | if (!customTextField.existsSync()) { 216 | customTextField.createSync(); 217 | customTextField.writeAsStringSync(''' 218 | import 'package:flutter/material.dart'; 219 | import 'package:$projectName/constants/dimensions.dart'; 220 | 221 | class CustomTextField extends StatefulWidget { 222 | final TextEditingController controller; 223 | final String label; 224 | final bool obscure; 225 | const CustomTextField({ 226 | this.obscure = false, 227 | required this.controller, 228 | required this.label, 229 | super.key, 230 | }); 231 | 232 | @override 233 | State createState() => _CustomTextFieldState(); 234 | } 235 | 236 | class _CustomTextFieldState extends State { 237 | @override 238 | Widget build(BuildContext context) { 239 | return Container( 240 | margin: EdgeInsets.symmetric( 241 | horizontal: getScreenWidth(context) * 0.05, 242 | vertical: getScreenWidth(context) * 0.04, 243 | ), 244 | child: SizedBox( 245 | child: TextField( 246 | obscureText: widget.obscure, 247 | controller: widget.controller, 248 | style: const TextStyle(color: Colors.black), 249 | decoration: InputDecoration( 250 | enabledBorder: OutlineInputBorder( 251 | borderSide: const BorderSide(color: Colors.white), 252 | borderRadius: BorderRadius.circular(8)), 253 | filled: true, 254 | labelText: widget.label, 255 | fillColor: const Color.fromARGB(255, 247, 247, 247), 256 | focusedBorder: OutlineInputBorder( 257 | borderRadius: BorderRadius.circular(8), 258 | borderSide: const BorderSide(color: Colors.white))), 259 | ), 260 | ), 261 | ); 262 | } 263 | } 264 | 265 | 266 | '''); 267 | } 268 | 269 | for (String feature in featuresArray) { 270 | // make the first letter of feature capital 271 | String cFeature = feature[0].toUpperCase() + feature.substring(1); 272 | navbar.writeAsStringSync( 273 | "${cFeature}Screen(),\n", 274 | mode: FileMode.append, 275 | ); 276 | } 277 | navbar.writeAsStringSync( 278 | '];', 279 | mode: FileMode.append, 280 | ); 281 | navbar.writeAsStringSync( 282 | ''' 283 | @override 284 | Widget build(BuildContext context) { 285 | return Scaffold( 286 | body: _pages[_selectedTab], 287 | bottomNavigationBar: BottomNavigationBar( 288 | currentIndex: _selectedTab, 289 | onTap: (index) { 290 | _changeTab(index); 291 | }, 292 | items: const [ 293 | ''', 294 | mode: FileMode.append, 295 | ); 296 | 297 | for (var feature in featuresArray) { 298 | navbar.writeAsStringSync( 299 | "BottomNavigationBarItem(icon: Icon(Icons.home), label: '$feature'),\n", 300 | mode: FileMode.append, 301 | ); 302 | } 303 | navbar.writeAsStringSync(''' 304 | ], 305 | ), 306 | ); 307 | } 308 | } 309 | ''', mode: FileMode.append); 310 | 311 | // Import bloc and flutter_bloc to pubspec.yaml 312 | // Directory.current = projectDirectory.path; 313 | final ProcessResult addPackagesResult = await Process.run( 314 | 'dart', 315 | ['pub', 'add', 'bloc', 'flutter_bloc'], 316 | ); 317 | if (addPackagesResult.exitCode != 0) { 318 | cliHandler.clearScreen(); 319 | cliHandler.printErrorText( 320 | 'Error adding packages. Check your internet connection and try again.'); 321 | print(addPackagesResult.stderr); 322 | return; 323 | } 324 | cliHandler.clearScreen(); 325 | 326 | // Go inside every feature bloc folder 327 | for (String feature in featuresArray) { 328 | String featureName = feature[0].toUpperCase() + feature.substring(1); 329 | final Directory blocFolder = Directory('lib/features/$feature/bloc'); 330 | if (blocFolder.existsSync()) { 331 | final File blocFile = 332 | File('lib/features/$feature/bloc/${feature}_bloc.dart'); 333 | final File eventFile = 334 | File('lib/features/$feature/bloc/${feature}_event.dart'); 335 | final File stateFile = 336 | File('lib/features/$feature/bloc/${feature}_state.dart'); 337 | 338 | // write bloc file 339 | if (!blocFile.existsSync()) { 340 | blocFile.createSync(); 341 | blocFile.writeAsStringSync(''' 342 | import 'package:bloc/bloc.dart'; 343 | import 'package:meta/meta.dart'; 344 | 345 | part '${feature}_event.dart'; 346 | part '${feature}_state.dart'; 347 | 348 | class ${featureName}Bloc extends Bloc<${featureName}Event, ${featureName}State> { 349 | ${featureName}Bloc() : super(${featureName}Initial()) { 350 | on<${featureName}Event>((event, emit) { 351 | // TODO: implement event handler 352 | }); 353 | } 354 | } 355 | 356 | '''); 357 | } 358 | 359 | // write event file 360 | if (!eventFile.existsSync()) { 361 | eventFile.createSync(); 362 | eventFile.writeAsStringSync(''' 363 | part of '${feature}_bloc.dart'; 364 | 365 | @immutable 366 | sealed class ${featureName}Event {} 367 | 368 | '''); 369 | } 370 | 371 | // write state file 372 | if (!stateFile.existsSync()) { 373 | stateFile.createSync(); 374 | stateFile.writeAsStringSync(''' 375 | part of '${feature}_bloc.dart'; 376 | 377 | @immutable 378 | sealed class ${featureName}State {} 379 | 380 | final class ${featureName}Initial extends ${featureName}State {} 381 | 382 | 383 | '''); 384 | } 385 | } 386 | } 387 | } 388 | 389 | static Future createConstants(String projectName) async { 390 | final constantsFolder = Directory('lib/constants'); 391 | constantsFolder.createSync(); 392 | 393 | final File dimensionsFile = File('lib/constants/dimensions.dart'); 394 | if (!dimensionsFile.existsSync()) { 395 | dimensionsFile.createSync(); 396 | dimensionsFile.writeAsStringSync(''' 397 | import 'package:flutter/widgets.dart'; 398 | 399 | final GlobalKey navigatorKey = GlobalKey(); 400 | 401 | double getScreenWidth(BuildContext context) { 402 | return MediaQuery.of(context).size.width; 403 | } 404 | 405 | double getScreenheight(BuildContext context) { 406 | return MediaQuery.of(context).size.height; 407 | } 408 | 409 | '''); 410 | } 411 | } 412 | } 413 | -------------------------------------------------------------------------------- /lib/core/create_project/main_handler.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | class MainFileHandler { 4 | static Future createNotificationSystemMainWithOnboarding( 5 | String projectName) async { 6 | final File mainFile = File('lib/main.dart'); 7 | mainFile.writeAsStringSync(''' 8 | import 'package:firebase_auth/firebase_auth.dart'; 9 | import 'package:firebase_messaging/firebase_messaging.dart'; 10 | import 'package:firebase_core/firebase_core.dart'; 11 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 12 | import 'package:shared_preferences/shared_preferences.dart'; 13 | import 'package:flutter/material.dart'; 14 | import 'package:flutter_local_notifications/flutter_local_notifications.dart'; 15 | import 'package:$projectName/firebase_options.dart'; 16 | import 'package:$projectName/features/auth/ui/auth_screen.dart'; 17 | import 'package:$projectName/features/onBoarding/ui/on_boarding_screen.dart'; 18 | import 'package:$projectName/shared/nav_bar.dart'; 19 | import 'package:$projectName/features/notification/services/notification_services.dart'; 20 | 21 | void main() async { 22 | WidgetsFlutterBinding.ensureInitialized(); 23 | await Firebase.initializeApp( 24 | options: DefaultFirebaseOptions.currentPlatform, 25 | ); 26 | await FirebaseMessaging.instance.requestPermission(); 27 | final token = await FirebaseMessaging.instance.getToken(); 28 | await NotificationService.initLocalNotifications(); 29 | 30 | FirebaseMessaging.onMessage.listen((RemoteMessage message) { 31 | print('Got a message whilst in the foreground!'); 32 | print('Message data: \${message.data}'); 33 | print(message.data['body']); 34 | 35 | NotificationService.showLocalNotification(message); 36 | 37 | 38 | if (message.notification != null) { 39 | print('Message also contained a notification: \${message.notification}'); 40 | } 41 | 42 | }); 43 | 44 | 45 | SharedPreferences prefs = await SharedPreferences.getInstance(); 46 | bool hasSeenOnboarding = prefs.getBool('hasSeenOnboarding') ?? false; 47 | runApp(ProviderScope(child: MyApp(hasSeenOnboarding: hasSeenOnboarding))); 48 | } 49 | 50 | final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = 51 | FlutterLocalNotificationsPlugin(); 52 | 53 | class MyApp extends StatelessWidget { 54 | final bool hasSeenOnboarding; 55 | const MyApp({required this.hasSeenOnboarding, super.key}); 56 | 57 | @override 58 | Widget build(BuildContext context) { 59 | return MaterialApp( 60 | debugShowCheckedModeBanner: false, 61 | theme: ThemeData( 62 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), 63 | useMaterial3: true, 64 | ), 65 | home: !hasSeenOnboarding ? const OnBoardingScreen(): StreamBuilder( 66 | stream: FirebaseAuth.instance.authStateChanges(), 67 | builder: (BuildContext context, AsyncSnapshot snapshot) { 68 | if (snapshot.hasError) { 69 | return Text(snapshot.error.toString()); 70 | } 71 | if (snapshot.connectionState == ConnectionState.active) { 72 | if (snapshot.data == null) { 73 | return const AuthScreen(); 74 | } else { 75 | return const NavigationScreen(); 76 | } 77 | } 78 | return const Center( 79 | child: CircularProgressIndicator(), 80 | ); 81 | }, 82 | ), 83 | ); 84 | } 85 | } 86 | 87 | 88 | 89 | '''); 90 | } 91 | 92 | static Future createNotficationSystemMainWithoutOnboarding( 93 | String projectName) async { 94 | final File mainFile = File('lib/main.dart'); 95 | mainFile.writeAsStringSync(''' 96 | import 'package:firebase_auth/firebase_auth.dart'; 97 | import 'package:firebase_core/firebase_core.dart'; 98 | import 'package:flutter/material.dart'; 99 | import 'package:$projectName/firebase_options.dart'; 100 | import 'package:$projectName/features/auth/ui/auth_screen.dart'; 101 | import 'package:$projectName/shared/nav_bar.dart'; 102 | import 'package:$projectName/features/notification/services/notification_services.dart'; 103 | import 'package:flutter_local_notifications/flutter_local_notifications.dart'; 104 | 105 | void main() async { 106 | WidgetsFlutterBinding.ensureInitialized(); 107 | await Firebase.initializeApp( 108 | options: DefaultFirebaseOptions.currentPlatform, 109 | ); 110 | runApp(const MyApp()); 111 | await FirebaseMessaging.instance.requestPermission(); 112 | final token = await FirebaseMessaging.instance.getToken(); 113 | await NotificationService.initLocalNotifications(); 114 | 115 | FirebaseMessaging.onMessage.listen((RemoteMessage message) { 116 | print('Got a message whilst in the foreground!'); 117 | print('Message data: \${message.data}'); 118 | print(message.data['body']); 119 | 120 | NotificationService.showLocalNotification(message); 121 | 122 | 123 | if (message.notification != null) { 124 | print('Message also contained a notification: \$+{message.notification}'); 125 | } 126 | 127 | }); 128 | } 129 | 130 | final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = 131 | FlutterLocalNotificationsPlugin(); 132 | 133 | 134 | class MyApp extends StatelessWidget { 135 | const MyApp({super.key}); 136 | 137 | @override 138 | Widget build(BuildContext context) { 139 | return MaterialApp( 140 | debugShowCheckedModeBanner: false, 141 | theme: ThemeData( 142 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), 143 | useMaterial3: true, 144 | ), 145 | home: StreamBuilder( 146 | stream: FirebaseAuth.instance.authStateChanges(), 147 | builder: (BuildContext context, AsyncSnapshot snapshot) { 148 | if (snapshot.hasError) { 149 | return Text(snapshot.error.toString()); 150 | } 151 | if (snapshot.connectionState == ConnectionState.active) { 152 | if (snapshot.data == null) { 153 | return const AuthScreen(); 154 | } else { 155 | return const NavigationScreen(); 156 | } 157 | } 158 | return const Center( 159 | child: CircularProgressIndicator(), 160 | ); 161 | }, 162 | ), 163 | ); 164 | } 165 | } 166 | 167 | 168 | '''); 169 | } 170 | 171 | static Future createFirebaseMainWithoutOnBoarding( 172 | String projectName) async { 173 | // change the content of main.dart 174 | final File mainFile = File('lib/main.dart'); 175 | mainFile.writeAsStringSync(''' 176 | import 'package:firebase_auth/firebase_auth.dart'; 177 | import 'package:firebase_core/firebase_core.dart'; 178 | import 'package:flutter/material.dart'; 179 | import 'package:$projectName/firebase_options.dart'; 180 | import 'package:$projectName/features/auth/ui/auth_screen.dart'; 181 | import 'package:$projectName/shared/nav_bar.dart'; 182 | 183 | void main() async { 184 | WidgetsFlutterBinding.ensureInitialized(); 185 | await Firebase.initializeApp( 186 | options: DefaultFirebaseOptions.currentPlatform, 187 | ); 188 | runApp(const MyApp()); 189 | } 190 | 191 | class MyApp extends StatelessWidget { 192 | const MyApp({super.key}); 193 | 194 | @override 195 | Widget build(BuildContext context) { 196 | return MaterialApp( 197 | debugShowCheckedModeBanner: false, 198 | theme: ThemeData( 199 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), 200 | useMaterial3: true, 201 | ), 202 | home: StreamBuilder( 203 | stream: FirebaseAuth.instance.authStateChanges(), 204 | builder: (BuildContext context, AsyncSnapshot snapshot) { 205 | if (snapshot.hasError) { 206 | return Text(snapshot.error.toString()); 207 | } 208 | if (snapshot.connectionState == ConnectionState.active) { 209 | if (snapshot.data == null) { 210 | return const AuthScreen(); 211 | } else { 212 | return const NavigationScreen(); 213 | } 214 | } 215 | return const Center( 216 | child: CircularProgressIndicator(), 217 | ); 218 | }, 219 | ), 220 | ); 221 | } 222 | } 223 | 224 | '''); 225 | } 226 | 227 | static Future createFirebaseMainWithOnBoarding( 228 | String projectName) async { 229 | // change the content of main.dart 230 | final File mainFile = File('lib/main.dart'); 231 | mainFile.writeAsStringSync(''' 232 | import 'package:firebase_auth/firebase_auth.dart'; 233 | import 'package:firebase_core/firebase_core.dart'; 234 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 235 | import 'package:shared_preferences/shared_preferences.dart'; 236 | import 'package:flutter/material.dart'; 237 | import 'package:$projectName/firebase_options.dart'; 238 | import 'package:$projectName/features/auth/ui/auth_screen.dart'; 239 | import 'package:$projectName/features/onBoarding/ui/on_boarding_screen.dart'; 240 | import 'package:$projectName/shared/nav_bar.dart'; 241 | 242 | void main() async { 243 | WidgetsFlutterBinding.ensureInitialized(); 244 | await Firebase.initializeApp( 245 | options: DefaultFirebaseOptions.currentPlatform, 246 | ); 247 | SharedPreferences prefs = await SharedPreferences.getInstance(); 248 | bool hasSeenOnboarding = prefs.getBool('hasSeenOnboarding') ?? false; 249 | runApp(ProviderScope(child: MyApp(hasSeenOnboarding: hasSeenOnboarding))); 250 | } 251 | 252 | class MyApp extends StatelessWidget { 253 | final bool hasSeenOnboarding; 254 | const MyApp({required this.hasSeenOnboarding, super.key}); 255 | 256 | @override 257 | Widget build(BuildContext context) { 258 | return MaterialApp( 259 | debugShowCheckedModeBanner: false, 260 | theme: ThemeData( 261 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), 262 | useMaterial3: true, 263 | ), 264 | home: !hasSeenOnboarding ? const OnBoardingScreen(): StreamBuilder( 265 | stream: FirebaseAuth.instance.authStateChanges(), 266 | builder: (BuildContext context, AsyncSnapshot snapshot) { 267 | if (snapshot.hasError) { 268 | return Text(snapshot.error.toString()); 269 | } 270 | if (snapshot.connectionState == ConnectionState.active) { 271 | if (snapshot.data == null) { 272 | return const AuthScreen(); 273 | } else { 274 | return const NavigationScreen(); 275 | } 276 | } 277 | return const Center( 278 | child: CircularProgressIndicator(), 279 | ); 280 | }, 281 | ), 282 | ); 283 | } 284 | } 285 | 286 | '''); 287 | } 288 | 289 | static Future createAppwriteMainWithoutOnBoarding( 290 | String projectName) async { 291 | final File mainFile = File('lib/main.dart'); 292 | mainFile.writeAsStringSync(''' 293 | import 'package:flutter/material.dart'; 294 | import 'package:appwrite/appwrite.dart'; 295 | import 'package:$projectName/features/auth/service/auth_status.dart'; 296 | import 'package:$projectName/features/auth/ui/auth_screen.dart'; 297 | import 'package:provider/provider.dart'; 298 | import 'package:$projectName/shared/nav_bar.dart'; 299 | 300 | void main() async { 301 | WidgetsFlutterBinding.ensureInitialized(); 302 | Client client = Client(); 303 | client = Client() 304 | .setEndpoint("") 305 | .setProject(""); 306 | Account account = Account(client); 307 | 308 | runApp(ChangeNotifierProvider( 309 | create: (context) => AuthService(), 310 | child: MyApp( 311 | account: account, 312 | ), 313 | )); 314 | } 315 | 316 | class MyApp extends StatelessWidget { 317 | final Account account; 318 | const MyApp({ 319 | required this.account, 320 | super.key, 321 | }); 322 | 323 | @override 324 | Widget build(BuildContext context) { 325 | final value = context.watch().status; 326 | return MaterialApp( 327 | debugShowCheckedModeBanner: false, 328 | theme: ThemeData( 329 | useMaterial3: true, 330 | ), 331 | home: value == AuthStatus.uninitialized 332 | ? const Scaffold( 333 | body: Center(child: CircularProgressIndicator()), 334 | ) 335 | : value == AuthStatus.authenticated 336 | ? const NavigationScreen() 337 | : const AuthScreen(), 338 | ); 339 | } 340 | } 341 | 342 | '''); 343 | } 344 | 345 | static Future createAppwriteMainWithOnBoarding( 346 | String projectName) async { 347 | final File mainFile = File('lib/main.dart'); 348 | mainFile.writeAsStringSync(''' 349 | 350 | import 'package:flutter/material.dart'; 351 | import 'package:appwrite/appwrite.dart'; 352 | import 'package:$projectName/features/auth/service/auth_status.dart'; 353 | import 'package:$projectName/features/auth/ui/auth_screen.dart'; 354 | import 'package:provider/provider.dart'; 355 | import 'package:shared_preferences/shared_preferences.dart'; 356 | import 'package:$projectName/features/onBoarding/ui/on_boarding_screen.dart'; 357 | import 'package:$projectName/shared/nav_bar.dart'; 358 | 359 | void main() async { 360 | WidgetsFlutterBinding.ensureInitialized(); 361 | Client client = Client(); 362 | client = Client() 363 | .setEndpoint("") 364 | .setProject(""); 365 | Account account = Account(client); 366 | SharedPreferences prefs = await SharedPreferences.getInstance(); 367 | bool hasSeenOnboarding = 368 | prefs.getBool('hasSeenOnboarding') ?? false; 369 | runApp(ChangeNotifierProvider( 370 | create: (context) => AuthService(), 371 | child: MyApp(account: account, hasSeenOnboarding: hasSeenOnboarding,), 372 | )); 373 | } 374 | 375 | class MyApp extends StatelessWidget { 376 | final Account account; 377 | final bool hasSeenOnboarding; 378 | const MyApp({ 379 | required this.account, 380 | required this.hasSeenOnboarding, 381 | super.key, 382 | }); 383 | 384 | @override 385 | Widget build(BuildContext context) { 386 | final value = context.watch().status; 387 | return MaterialApp( 388 | debugShowCheckedModeBanner: false, 389 | theme: ThemeData( 390 | useMaterial3: true, 391 | ), 392 | home: !hasSeenOnboarding 393 | ? const OnBoardingScreen() 394 | : value == AuthStatus.uninitialized 395 | ? const Scaffold( 396 | body: Center(child: CircularProgressIndicator()), 397 | ) 398 | : value == AuthStatus.authenticated 399 | ? const NavigationScreen() 400 | : const AuthScreen(), 401 | ); 402 | } 403 | } 404 | 405 | 406 | 407 | '''); 408 | } 409 | 410 | static Future createNoAuthMainFileWithOnBoarding( 411 | String projectName) async { 412 | final File mainFile = File('lib/main.dart'); 413 | mainFile.writeAsStringSync(''' 414 | import 'package:flutter/material.dart'; 415 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 416 | import 'package:shared_preferences/shared_preferences.dart'; 417 | import 'package:$projectName/features/onBoarding/ui/on_boarding_screen.dart'; 418 | import 'package:$projectName/shared/nav_bar.dart'; 419 | 420 | void main() async { 421 | WidgetsFlutterBinding.ensureInitialized(); 422 | SharedPreferences prefs = await SharedPreferences.getInstance(); 423 | bool hasSeenOnboarding = prefs.getBool('hasSeenOnboarding') ?? false; 424 | runApp(ProviderScope(child: MyApp(hasSeenOnboarding: hasSeenOnboarding))); 425 | } 426 | 427 | class MyApp extends StatelessWidget { 428 | final bool hasSeenOnboarding; 429 | const MyApp({required this.hasSeenOnboarding, super.key}); 430 | 431 | @override 432 | Widget build(BuildContext context) { 433 | return MaterialApp( 434 | debugShowCheckedModeBanner: false, 435 | theme: ThemeData( 436 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), 437 | useMaterial3: true, 438 | ), 439 | home: !hasSeenOnboarding 440 | ? const OnBoardingScreen() 441 | : const NavigationScreen(), 442 | ); 443 | } 444 | } 445 | 446 | '''); 447 | } 448 | 449 | static Future createNoAuthMainFileWithoutOnBoarding( 450 | String projectName) async { 451 | final File mainFile = File('lib/main.dart'); 452 | mainFile.writeAsStringSync(''' 453 | import 'package:flutter/material.dart'; 454 | import 'package:$projectName/shared/nav_bar.dart'; 455 | 456 | void main() { 457 | runApp(const MyApp()); 458 | } 459 | 460 | class MyApp extends StatelessWidget { 461 | const MyApp({super.key}); 462 | 463 | 464 | @override 465 | Widget build(BuildContext context) { 466 | return MaterialApp( 467 | theme: ThemeData( 468 | useMaterial3: true, 469 | ), 470 | home: const NavigationScreen(), 471 | ); 472 | } 473 | } 474 | 475 | '''); 476 | } 477 | } 478 | -------------------------------------------------------------------------------- /lib/core/create_project/auth_handler.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | class AuthHandler { 4 | static Future implementAppwrite(String projectName) async { 5 | // Import appwrite sdk 6 | final ProcessResult addPackagesResult = await Process.run( 7 | 'dart', 8 | ['pub', 'add', 'appwrite'], 9 | ); 10 | final ProcessResult addProviderResult = await Process.run( 11 | 'dart', 12 | ['pub', 'add', 'provider'], 13 | ); 14 | 15 | if (addPackagesResult.exitCode != 0) { 16 | print( 17 | 'Error adding appwrite packages. Check your internet connection and try again.'); 18 | print(addPackagesResult.stderr); 19 | return; 20 | } 21 | print('added appwrite sdk to project'); 22 | 23 | if (addProviderResult.exitCode != 0) { 24 | print( 25 | 'Error adding provider package. Check your internet connection and try again.'); 26 | print(addProviderResult.stderr); 27 | return; 28 | } 29 | print('added appwrite sdk to project'); 30 | 31 | // create auth folder 32 | final authFolder = Directory('lib/features/auth'); 33 | authFolder.createSync(); 34 | 35 | // create auth service folder 36 | final authServiceFolder = Directory('lib/features/auth/service'); 37 | authServiceFolder.createSync(); 38 | 39 | final File authStatusFile = 40 | File('lib/features/auth/service/auth_status.dart'); 41 | 42 | if (!authStatusFile.existsSync()) { 43 | authStatusFile.createSync(); 44 | authStatusFile.writeAsStringSync(""" 45 | // ignore_for_file: avoid_print 46 | 47 | import 'package:appwrite/appwrite.dart'; 48 | import 'package:appwrite/models.dart'; 49 | import 'package:flutter/material.dart'; 50 | 51 | enum AuthStatus { 52 | uninitialized, 53 | authenticated, 54 | unauthenticated, 55 | } 56 | 57 | class AuthService extends ChangeNotifier { 58 | Client client = Client(); 59 | late final Account account; 60 | 61 | late User _currentUser; 62 | 63 | AuthStatus _status = AuthStatus.uninitialized; 64 | 65 | // Getter methods 66 | User get currentUser => _currentUser; 67 | AuthStatus get status => _status; 68 | String? get username => _currentUser.name; 69 | String? get email => _currentUser.email; 70 | String? get userid => _currentUser.\$id; 71 | 72 | // Constructor 73 | AuthService() { 74 | init(); 75 | loadUser(); 76 | } 77 | 78 | // Initialize the Appwrite client 79 | init() { 80 | client 81 | .setEndpoint('replace_with_your_endpoint') 82 | .setProject('replace_with_project_id') 83 | .setSelfSigned(); 84 | account = Account(client); 85 | } 86 | 87 | loadUser() async { 88 | try { 89 | final user = await account.get(); 90 | _status = AuthStatus.authenticated; 91 | _currentUser = user; 92 | } catch (e) { 93 | _status = AuthStatus.unauthenticated; 94 | } finally { 95 | notifyListeners(); 96 | } 97 | } 98 | 99 | Future createUser( 100 | {required String email, required String password, required String name}) async { 101 | notifyListeners(); 102 | 103 | try { 104 | final user = await account.create( 105 | userId: ID.unique(), 106 | email: email, 107 | password: password, 108 | name: name); 109 | return user; 110 | } finally { 111 | notifyListeners(); 112 | } 113 | } 114 | 115 | Future createEmailSession( 116 | {required String email, required String password}) async { 117 | notifyListeners(); 118 | 119 | try { 120 | final session = 121 | await account.createEmailSession(email: email, password: password); 122 | _currentUser = await account.get(); 123 | _status = AuthStatus.authenticated; 124 | return session; 125 | } finally { 126 | notifyListeners(); 127 | } 128 | } 129 | 130 | signInWithProvider({required String provider}) async { 131 | try { 132 | final session = await account.createOAuth2Session(provider: provider); 133 | _currentUser = await account.get(); 134 | _status = AuthStatus.authenticated; 135 | return session; 136 | } finally { 137 | notifyListeners(); 138 | } 139 | } 140 | 141 | signOut() async { 142 | try { 143 | await account.deleteSession(sessionId: 'current'); 144 | _status = AuthStatus.unauthenticated; 145 | } finally { 146 | notifyListeners(); 147 | } 148 | } 149 | 150 | Future getUserPreferences() async { 151 | return await account.getPrefs(); 152 | } 153 | 154 | updatePreferences({required String bio}) async { 155 | return account.updatePrefs(prefs: {'bio': bio}); 156 | } 157 | } 158 | 159 | """); 160 | } 161 | 162 | // change the content of androidmanifest.xml 163 | final File androidManifest = 164 | File('android/app/src/main/AndroidManifest.xml'); 165 | androidManifest.writeAsStringSync(''' 166 | 167 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 187 | 191 | 195 | 196 | 197 | 198 | 199 | 200 | 202 | 205 | 206 | 207 | 208 | '''); 209 | 210 | // change the content of main.dart 211 | final File mainFile = File('lib/main.dart'); 212 | mainFile.writeAsStringSync(''' 213 | import 'package:flutter/material.dart'; 214 | import 'package:appwrite/appwrite.dart'; 215 | import 'package:$projectName/features/auth/service/auth_status.dart'; 216 | import 'package:$projectName/features/auth/ui/auth_screen.dart'; 217 | import 'package:provider/provider.dart'; 218 | import 'package:shared_preferences/shared_preferences.dart'; 219 | import 'package:$projectName/features/onBoarding/ui/on_boarding_screen.dart'; 220 | import 'package:$projectName/shared/nav_bar.dart'; 221 | 222 | void main() async { 223 | WidgetsFlutterBinding.ensureInitialized(); 224 | Client client = Client(); 225 | client = Client() 226 | .setEndpoint("") 227 | .setProject(""); 228 | Account account = Account(client); 229 | SharedPreferences prefs = await SharedPreferences.getInstance(); 230 | bool hasSeenOnboarding = 231 | prefs.getBool('hasSeenOnboarding') ?? false; 232 | runApp(ChangeNotifierProvider( 233 | create: (context) => AuthService(), 234 | child: MyApp(account: account, hasSeenOnboarding: hasSeenOnboarding,), 235 | )); 236 | } 237 | 238 | class MyApp extends StatelessWidget { 239 | final Account account; 240 | final bool hasSeenOnboarding; 241 | const MyApp({ 242 | required this.account, 243 | required this.hasSeenOnboarding, 244 | super.key, 245 | }); 246 | 247 | @override 248 | Widget build(BuildContext context) { 249 | final value = context.watch().status; 250 | return MaterialApp( 251 | debugShowCheckedModeBanner: false, 252 | theme: ThemeData( 253 | useMaterial3: true, 254 | ), 255 | home: !hasSeenOnboarding 256 | ? const OnBoardingScreen() 257 | : value == AuthStatus.uninitialized 258 | ? const Scaffold( 259 | body: Center(child: CircularProgressIndicator()), 260 | ) 261 | : value == AuthStatus.authenticated 262 | ? const NavigationScreen() 263 | : const AuthScreen(), 264 | ); 265 | } 266 | } 267 | 268 | '''); 269 | 270 | // create auth_screen.dart 271 | final authUiFolder = Directory('lib/features/auth/ui'); 272 | if (!authUiFolder.existsSync()) { 273 | authUiFolder.createSync(); 274 | final File authScreen = File('lib/features/auth/ui/auth_screen.dart'); 275 | authScreen.createSync(); 276 | authScreen.writeAsStringSync(''' 277 | import 'package:flutter/material.dart'; 278 | import 'package:appwrite/models.dart' as models; 279 | import 'package:$projectName/constants/dimensions.dart'; 280 | import 'package:$projectName/features/auth/ui/login_screen.dart'; 281 | import 'package:$projectName/features/auth/ui/register_screen.dart'; 282 | import 'package:$projectName/shared/custom_button.dart'; 283 | 284 | class AuthScreen extends StatefulWidget { 285 | const AuthScreen({super.key}); 286 | 287 | 288 | @override 289 | State createState() => _AuthScreenState(); 290 | } 291 | 292 | class _AuthScreenState extends State { 293 | models.User? loggedInUser; 294 | @override 295 | Widget build(BuildContext context) { 296 | return Scaffold( 297 | body: SafeArea( 298 | child: Column( 299 | crossAxisAlignment: CrossAxisAlignment.center, 300 | mainAxisAlignment: MainAxisAlignment.center, 301 | children: [ 302 | ClipRRect( 303 | borderRadius: BorderRadius.circular(20), 304 | child: Image.asset('assets/img/logo.png', 305 | width: getScreenWidth(context) * 0.7)), 306 | SizedBox(height: getScreenheight(context) * 0.02), 307 | const Text( 308 | 'Made with quickfire', 309 | style: TextStyle(fontWeight: FontWeight.w800), 310 | ), 311 | SizedBox(height: getScreenheight(context) * 0.13), 312 | CustomButton( 313 | text: 'Sign up', 314 | function: () { 315 | Navigator.push( 316 | context, 317 | MaterialPageRoute( 318 | builder: (context) => const RegisterScreen())); 319 | }), 320 | CustomButton(text: 'Continue with Phone number', function: () {}), 321 | CustomButton(text: 'Continue with Google', function: () {}), 322 | CustomButton(text: 'Continue with Facebook', function: () {}), 323 | CustomButton( 324 | text: 'Login', 325 | function: () { 326 | Navigator.push( 327 | context, 328 | MaterialPageRoute( 329 | builder: (context) => const LogInScreen(), 330 | ), 331 | ); 332 | }), 333 | ], 334 | ), 335 | ), 336 | ); 337 | } 338 | } 339 | 340 | '''); 341 | } 342 | 343 | // Create login screen 344 | final File authLoginScreen = File('lib/features/auth/ui/login_screen.dart'); 345 | authLoginScreen.createSync(); 346 | authLoginScreen.writeAsStringSync(''' 347 | // ignore_for_file: use_build_context_synchronously 348 | 349 | import 'package:flutter/material.dart'; 350 | import 'package:appwrite/appwrite.dart'; 351 | import 'package:$projectName/features/auth/service/auth_status.dart'; // service/auth_status.dart 352 | import 'package:provider/provider.dart'; 353 | import 'package:$projectName/shared/custom_button.dart'; 354 | import 'package:$projectName/shared/custom_text_field.dart'; 355 | import 'package:$projectName/shared/nav_bar.dart'; 356 | 357 | class LogInScreen extends StatefulWidget { 358 | const LogInScreen({super.key}); 359 | 360 | @override 361 | State createState() => _LogInScreenState(); 362 | } 363 | 364 | class _LogInScreenState extends State { 365 | final _emailController = TextEditingController(); 366 | final _passwordController = TextEditingController(); 367 | bool loading = false; 368 | 369 | // sign in func 370 | signIn() async { 371 | showDialog( 372 | context: context, 373 | barrierDismissible: false, 374 | builder: (BuildContext context) { 375 | return const Dialog( 376 | backgroundColor: Colors.transparent, 377 | child: Row( 378 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 379 | children: [ 380 | CircularProgressIndicator(), 381 | ], 382 | ), 383 | ); 384 | }); 385 | 386 | try { 387 | final AuthService appwrite = context.read(); 388 | await appwrite.createEmailSession( 389 | email: _emailController.text, 390 | password: _passwordController.text, 391 | ); 392 | Navigator.pop(context); 393 | Navigator.pushReplacement(context, 394 | MaterialPageRoute(builder: (context) => const NavigationScreen())); 395 | } on AppwriteException catch (e) { 396 | Navigator.pop(context); 397 | showAlert(title: 'Login failed', text: e.message.toString()); 398 | } 399 | } 400 | 401 | showAlert({required String title, required String text}) { 402 | showDialog( 403 | context: context, 404 | builder: (context) { 405 | return AlertDialog( 406 | title: Text(title), 407 | content: Text(text), 408 | actions: [ 409 | ElevatedButton( 410 | onPressed: () { 411 | Navigator.pop(context); 412 | }, 413 | child: const Text('OK')) 414 | ], 415 | ); 416 | }); 417 | } 418 | 419 | @override 420 | Widget build(BuildContext context) { 421 | return Scaffold( 422 | appBar: AppBar(), 423 | body: SafeArea( 424 | child: Column( 425 | crossAxisAlignment: CrossAxisAlignment.center, 426 | children: [ 427 | CustomTextField( 428 | controller: _emailController, label: 'Enter your email'), 429 | CustomTextField( 430 | controller: _passwordController, 431 | label: 'Enter your pssword', 432 | obscure: true, 433 | ), 434 | CustomButton( 435 | text: 'Sign in', 436 | function: () { 437 | signIn(); 438 | }) 439 | ], 440 | )), 441 | ); 442 | } 443 | } 444 | 445 | '''); 446 | 447 | // Create registration screen 448 | final File authRegisterScreen = 449 | File('lib/features/auth/ui/register_screen.dart'); 450 | authRegisterScreen.createSync(); 451 | authRegisterScreen.writeAsStringSync(''' 452 | // ignore_for_file: use_build_context_synchronously 453 | 454 | import 'package:flutter/material.dart'; 455 | import 'package:appwrite/appwrite.dart'; 456 | import 'package:provider/provider.dart'; 457 | import 'package:$projectName/shared/custom_button.dart'; 458 | import 'package:$projectName/shared/custom_text_field.dart'; 459 | import 'package:$projectName/shared/nav_bar.dart'; 460 | import 'package:$projectName/features/auth/service/auth_status.dart'; 461 | 462 | class RegisterScreen extends StatefulWidget { 463 | const RegisterScreen({super.key}); 464 | 465 | @override 466 | State createState() => _RegisterScreenState(); 467 | } 468 | 469 | class _RegisterScreenState extends State { 470 | final _emailController = TextEditingController(); 471 | final _passwordController = TextEditingController(); 472 | final _nameController = TextEditingController(); 473 | 474 | createAccount() async { 475 | showDialog( 476 | context: context, 477 | barrierDismissible: false, 478 | builder: (BuildContext context) { 479 | return const Dialog( 480 | backgroundColor: Colors.transparent, 481 | child: Row( 482 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 483 | children: [CircularProgressIndicator()], 484 | ), 485 | ); 486 | }); 487 | try { 488 | final AuthService appwrite = context.read(); 489 | await appwrite.createUser( 490 | email: _emailController.text, 491 | password: _passwordController.text, 492 | name: _nameController.text, 493 | ); 494 | Navigator.pop(context); 495 | const snackbar = SnackBar(content: Text('Account created!')); 496 | ScaffoldMessenger.of(context).showSnackBar(snackbar); 497 | Navigator.pushReplacement( 498 | context, 499 | (MaterialPageRoute( 500 | builder: (context) => const NavigationScreen(), 501 | )), 502 | ); 503 | } on AppwriteException catch (e) { 504 | Navigator.pop(context); 505 | showAlert(title: 'Account creation failed', text: e.message.toString()); 506 | } 507 | } 508 | 509 | showAlert({required String title, required String text}) { 510 | showDialog( 511 | context: context, 512 | builder: (context) { 513 | return AlertDialog( 514 | title: Text(title), 515 | content: Text(text), 516 | actions: [ 517 | TextButton( 518 | onPressed: () { 519 | Navigator.pop(context); 520 | }, 521 | child: const Text('Ok')) 522 | ], 523 | ); 524 | }); 525 | } 526 | 527 | @override 528 | Widget build(BuildContext context) { 529 | return Scaffold( 530 | appBar: AppBar(), 531 | body: SafeArea( 532 | child: Column( 533 | crossAxisAlignment: CrossAxisAlignment.start, 534 | children: [ 535 | CustomTextField( 536 | controller: _nameController, label: 'Enter your name!'), 537 | CustomTextField( 538 | controller: _emailController, label: 'Enter your email!'), 539 | CustomTextField( 540 | controller: _passwordController, 541 | label: 'Enter your password!', 542 | obscure: true, 543 | ), 544 | CustomButton( 545 | text: 'Create new account!', 546 | function: () { 547 | createAccount(); 548 | }), 549 | ], 550 | )), 551 | ); 552 | } 553 | } 554 | 555 | '''); 556 | } 557 | 558 | static Future implementFirebase(String projectName) async { 559 | // Import firebase core and firebase auth 560 | final ProcessResult addFirebaseCore = 561 | await Process.run('dart', ['pub', 'add', 'firebase_core']); 562 | final ProcessResult addFirebaseAuth = 563 | await Process.run('dart', ['pub', 'add', 'firebase_auth']); 564 | final ProcessResult addGoogleSignIn = 565 | await Process.run('dart', ['pub', 'add', 'google_sign_in']); 566 | 567 | if (addFirebaseCore.exitCode != 0) { 568 | print( 569 | 'Error adding packages. Check your internet connection and try again.'); 570 | print(addFirebaseCore.stderr); 571 | return; 572 | } 573 | if (addFirebaseAuth.exitCode != 0) { 574 | print( 575 | 'Error adding packages. Check your internet connection and try again.'); 576 | print(addFirebaseAuth.stderr); 577 | return; 578 | } 579 | if (addGoogleSignIn.exitCode != 0) { 580 | print( 581 | 'Error adding packages. Check your internet connection and try again.'); 582 | print(addGoogleSignIn.stderr); 583 | return; 584 | } 585 | 586 | print('added firebase packages'); 587 | 588 | // Create a login page 589 | 590 | // create lib/features/auth/ui 591 | final authFolder = Directory('lib/features/auth'); 592 | authFolder.createSync(); 593 | final authUiFolder = Directory('lib/features/auth/ui'); 594 | authUiFolder.createSync(); 595 | 596 | // Create login page inside lib/features/auth/ui/login_screen.dart 597 | final File loginScreen = File('lib/features/auth/ui/auth_screen.dart'); 598 | if (!loginScreen.existsSync()) { 599 | loginScreen.createSync(); 600 | loginScreen.writeAsStringSync(''' 601 | import 'package:flutter/material.dart'; 602 | import 'package:$projectName/features/auth/service/auth_service.dart'; 603 | import 'package:$projectName/shared/custom_button.dart'; 604 | class AuthScreen extends StatefulWidget { 605 | const AuthScreen({super.key}); 606 | 607 | @override 608 | State createState() => _AuthScreenState(); 609 | } 610 | 611 | class _AuthScreenState extends State { 612 | @override 613 | Widget build(BuildContext context) { 614 | return Scaffold( 615 | body: Center( 616 | child: CustomButton( 617 | text: 'Continue with google', 618 | function: () { 619 | AuthService().continueWithGoogle(context); 620 | }), 621 | ); 622 | } 623 | } 624 | 625 | '''); 626 | } 627 | 628 | final authServiceFolder = Directory('lib/features/auth/service'); 629 | authServiceFolder.createSync(); 630 | 631 | // create auth service inside lib/features/auth/service/auth_service.dart 632 | final File authService = 633 | File('lib/features/auth/service/auth_service.dart'); 634 | if (!authService.existsSync()) { 635 | authService.createSync(); 636 | authService.writeAsStringSync(''' 637 | import 'package:firebase_auth/firebase_auth.dart'; 638 | import 'package:flutter/material.dart'; 639 | import 'package:google_sign_in/google_sign_in.dart'; 640 | import 'package:$projectName/shared/nav_bar.dart'; 641 | 642 | 643 | 644 | class AuthService { 645 | final FirebaseAuth _auth = FirebaseAuth.instance; 646 | 647 | continueWithGoogle(BuildContext context) async { 648 | final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn(); 649 | if (googleUser != null) { 650 | final GoogleSignInAuthentication googleAuth = 651 | await googleUser.authentication; 652 | final credential = GoogleAuthProvider.credential( 653 | accessToken: googleAuth.accessToken, 654 | idToken: googleAuth.idToken, 655 | ); 656 | 657 | // Once signed in navigate to the home screen 658 | final UserCredential userCredential = 659 | await _auth.signInWithCredential(credential); 660 | final User? user = userCredential.user; 661 | if (user != null) { 662 | // ignore: use_build_context_synchronously 663 | Navigator.pushReplacement(context, 664 | MaterialPageRoute(builder: (context) => const NavigationScreen())); 665 | } 666 | return user; 667 | } else { 668 | // handle signin errors 669 | return null; 670 | } 671 | } 672 | } 673 | 674 | '''); 675 | } 676 | } 677 | } 678 | --------------------------------------------------------------------------------