├── .gradle ├── 5.1.1 │ ├── gc.properties │ ├── fileChanges │ │ └── last-build.bin │ └── fileHashes │ │ └── fileHashes.lock ├── vcs-1 │ └── gc.properties └── buildOutputCleanup │ ├── cache.properties │ └── buildOutputCleanup.lock ├── ios ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── icons.png │ │ │ ├── icons-1.png │ │ │ ├── icons-2.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Podfile └── Podfile.lock ├── assets ├── cv.png ├── in.png ├── mail.png ├── icons.png ├── mobile.png └── location.png ├── android ├── gradle.properties ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values │ │ │ │ │ └── styles.xml │ │ │ │ └── drawable │ │ │ │ │ └── launch_background.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── mayandro │ │ │ │ │ └── resumepad │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── build.gradle ├── lib ├── utility │ ├── text_utility.dart │ ├── fade_route_builder.dart │ ├── network_utility.dart │ ├── constants.dart │ ├── text_formatter │ │ ├── phone_text_formatter.dart │ │ └── date_text_formatter.dart │ └── color_utility.dart ├── screen │ ├── add_language │ │ ├── bloc │ │ │ ├── add_language_bloc.dart │ │ │ └── bloc_provider.dart │ │ └── add_language_dialog.dart │ ├── add_project │ │ ├── bloc │ │ │ ├── bloc_provider.dart │ │ │ └── add_project_bloc.dart │ │ └── add_project_dialog.dart │ ├── resume_maker │ │ ├── bloc │ │ │ ├── bloc_provider.dart │ │ │ └── resume_maker_bloc.dart │ │ ├── widget │ │ │ ├── onboarding_header_text.dart │ │ │ ├── page_indicator.dart │ │ │ ├── next_page_button.dart │ │ │ ├── previous_page_button.dart │ │ │ ├── user_name_widget.dart │ │ │ └── user_picture_widget.dart │ │ ├── tabs │ │ │ ├── all_done_tab.dart │ │ │ ├── profile_summary_tab.dart │ │ │ ├── skills_tab.dart │ │ │ ├── profile_tab.dart │ │ │ ├── language_tab.dart │ │ │ ├── reference_tab.dart │ │ │ ├── projects_tab.dart │ │ │ ├── experience_tab.dart │ │ │ ├── education_tab.dart │ │ │ └── contact_tab.dart │ │ └── resume_maker_page.dart │ ├── add_education │ │ ├── bloc │ │ │ ├── bloc_provider.dart │ │ │ └── add_education_bloc.dart │ │ └── add_education_dialog.dart │ ├── add_experience │ │ ├── bloc │ │ │ ├── bloc_provider.dart │ │ │ └── add_experience_bloc.dart │ │ └── add_experience_dialog.dart │ ├── preview_resume │ │ └── preview_resume_page.dart │ ├── on_boarding │ │ └── on_boarding_page.dart │ └── add_reference │ │ └── add_reference_dialog.dart ├── widget │ ├── circular_image_card.dart │ ├── rounded_button.dart │ ├── date_picker_field.dart │ └── custom_text_feild_form.dart ├── main.dart └── model │ └── resume_model.dart ├── .metadata ├── test └── widget_test.dart ├── LICENSE ├── README.md ├── .gitignore ├── pubspec.yaml └── pubspec.lock /.gradle/5.1.1/gc.properties: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gradle/vcs-1/gc.properties: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gradle/5.1.1/fileChanges/last-build.bin: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /.gradle/buildOutputCleanup/cache.properties: -------------------------------------------------------------------------------- 1 | #Mon Aug 19 16:07:45 CEST 2019 2 | gradle.version=5.1.1 3 | -------------------------------------------------------------------------------- /assets/cv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/assets/cv.png -------------------------------------------------------------------------------- /assets/in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/assets/in.png -------------------------------------------------------------------------------- /assets/mail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/assets/mail.png -------------------------------------------------------------------------------- /assets/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/assets/icons.png -------------------------------------------------------------------------------- /assets/mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/assets/mobile.png -------------------------------------------------------------------------------- /assets/location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/assets/location.png -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /.gradle/5.1.1/fileHashes/fileHashes.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/.gradle/5.1.1/fileHashes/fileHashes.lock -------------------------------------------------------------------------------- /.gradle/buildOutputCleanup/buildOutputCleanup.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/.gradle/buildOutputCleanup/buildOutputCleanup.lock -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/icons.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/icons-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/icons-1.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/icons-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/icons-2.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/flutter-resume-making-and-sharing-app/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/utility/text_utility.dart: -------------------------------------------------------------------------------- 1 | 2 | bool equalsIgnoreCase(String string1, String string2) { 3 | return string1?.toLowerCase() == string2?.toLowerCase(); 4 | } 5 | 6 | String format(double n) { 7 | return n.toStringAsFixed(n.truncateToDouble() == n ? 0 : 2); 8 | } 9 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 7 | 8 | 9 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 20e59316b8b8474554b38493b8ca888794b0234a 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/mayandro/resumepad/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.resumepad 2 | 3 | import android.os.Bundle 4 | 5 | import io.flutter.app.FlutterActivity 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | GeneratedPluginRegistrant.registerWith(this) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/screen/add_language/bloc/add_language_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:rxdart/rxdart.dart'; 4 | 5 | class AddLanguageBloc { 6 | double level = 0; 7 | 8 | final _levelSliderSubject = PublishSubject(); 9 | Stream get levelSliderStream => _levelSliderSubject.stream; 10 | Sink get levelSliderSink => _levelSliderSubject.sink; 11 | 12 | dispose() { 13 | _levelSliderSubject.close(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/utility/fade_route_builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class FadeRouteBuilder extends PageRouteBuilder { 4 | final Widget page; 5 | 6 | FadeRouteBuilder({@required this.page}) 7 | : super( 8 | pageBuilder: (context, animation1, animation2) => page, 9 | transitionsBuilder: (context, animation1, animation2, child) { 10 | return FadeTransition(opacity: animation1, child: child); 11 | }, 12 | ); 13 | } -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /lib/utility/network_utility.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | Future checkIsNetworkAvailable() async { 5 | bool statusConnected = false; 6 | try { 7 | final result = await InternetAddress.lookup('google.com'); 8 | if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) { 9 | print('connected'); 10 | statusConnected = true; 11 | } 12 | } on SocketException catch (_) { 13 | print('not connected'); 14 | } 15 | return statusConnected; 16 | } -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icons-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icons-2.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:resumepad/main.dart'; 12 | 13 | void main() { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /lib/screen/add_project/bloc/bloc_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'add_project_bloc.dart'; 4 | 5 | class AddProjectBlocProvider extends InheritedWidget { 6 | final AddProjectBloc bloc; 7 | 8 | const AddProjectBlocProvider({ 9 | Key key, 10 | @required this.bloc, 11 | Widget child, 12 | }) : super(key: key, child: child); 13 | 14 | @override 15 | bool updateShouldNotify(_) => true; 16 | 17 | static AddProjectBloc of(BuildContext context) { 18 | return (context.inheritFromWidgetOfExactType(AddProjectBlocProvider) as AddProjectBlocProvider).bloc; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/screen/add_language/bloc/bloc_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'add_language_bloc.dart'; 4 | 5 | class AddLanguageBlocProvider extends InheritedWidget { 6 | final AddLanguageBloc bloc; 7 | 8 | const AddLanguageBlocProvider({ 9 | Key key, 10 | @required this.bloc, 11 | Widget child, 12 | }) : super(key: key, child: child); 13 | 14 | @override 15 | bool updateShouldNotify(_) => true; 16 | 17 | static AddLanguageBloc of(BuildContext context) { 18 | return (context.inheritFromWidgetOfExactType(AddLanguageBlocProvider) as AddLanguageBlocProvider).bloc; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/screen/resume_maker/bloc/bloc_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'resume_maker_bloc.dart'; 4 | 5 | class ResumeMakerBlocProvider extends InheritedWidget { 6 | final ResumeMakerBloc bloc; 7 | 8 | const ResumeMakerBlocProvider({ 9 | Key key, 10 | @required this.bloc, 11 | Widget child, 12 | }) : super(key: key, child: child); 13 | 14 | @override 15 | bool updateShouldNotify(_) => true; 16 | 17 | static ResumeMakerBloc of(BuildContext context) { 18 | return (context.inheritFromWidgetOfExactType(ResumeMakerBlocProvider) as ResumeMakerBlocProvider).bloc; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/screen/add_education/bloc/bloc_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'add_education_bloc.dart'; 4 | 5 | class AddEducationBlocProvider extends InheritedWidget { 6 | final AddEducationBloc bloc; 7 | 8 | const AddEducationBlocProvider({ 9 | Key key, 10 | @required this.bloc, 11 | Widget child, 12 | }) : super(key: key, child: child); 13 | 14 | @override 15 | bool updateShouldNotify(_) => true; 16 | 17 | static AddEducationBloc of(BuildContext context) { 18 | return (context.inheritFromWidgetOfExactType(AddEducationBlocProvider) as AddEducationBlocProvider).bloc; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/screen/add_experience/bloc/bloc_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'add_experience_bloc.dart'; 4 | 5 | class AddExperienceBlocProvider extends InheritedWidget { 6 | final AddExperienceBloc bloc; 7 | 8 | const AddExperienceBlocProvider({ 9 | Key key, 10 | @required this.bloc, 11 | Widget child, 12 | }) : super(key: key, child: child); 13 | 14 | @override 15 | bool updateShouldNotify(_) => true; 16 | 17 | static AddExperienceBloc of(BuildContext context) { 18 | return (context.inheritFromWidgetOfExactType(AddExperienceBlocProvider) as AddExperienceBlocProvider).bloc; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/utility/constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:resumepad/utility/color_utility.dart'; 2 | 3 | const NAVIGATE_TO_PROFILE_TAB = 0; 4 | const NAVIGATE_TO_PROFILE_SUMMARY_TAB = 1; 5 | const NAVIGATE_TO_CONTACT_TAB = 2; 6 | const NAVIGATE_TO_EDUCATION_TAB = 3; 7 | const NAVIGATE_TO_EXPERIENCE_TAB = 4; 8 | const NAVIGATE_TO_PROJECTS_TAB = 5; 9 | const NAVIGATE_TO_REFERENCES_TAB = 7; 10 | const NAVIGATE_TO_SKILLS_TAB = 6; 11 | const NAVIGATE_TO_LANGUAGE_TAB = 8; 12 | const NAVIGATE_TO_ALL_DONE_TAB = 9; 13 | 14 | const SELECTED_THEME = 'SELECTED_THEME'; 15 | const SELECTED_THEME_RED = 0; 16 | const SELECTED_THEME_BLUE = 1; 17 | const SELECTED_THEME_GREEN = 2; 18 | const SELECTED_THEME_YELLOW = 3; 19 | const SELECTED_THEME_DARK = 3; 20 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.2.71' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.2.1' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /lib/widget/circular_image_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CircularImageCard extends StatelessWidget { 4 | CircularImageCard({@required this.image, @required this.scale}); 5 | 6 | final String image; 7 | final double scale; 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Card( 12 | elevation: (1 + scale) * 4, 13 | clipBehavior: Clip.antiAlias, 14 | shape: CircleBorder( 15 | side: BorderSide(color: Colors.grey.shade200, width: 0)), 16 | child: Padding( 17 | padding: EdgeInsets.all(8 * (1 + scale)), 18 | child: Image.asset( 19 | image, 20 | fit: BoxFit.contain, 21 | ), 22 | ), 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/screen/resume_maker/widget/onboarding_header_text.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:resumepad/utility/color_utility.dart'; 3 | 4 | class OnBoardingHeaderText extends StatelessWidget { 5 | final String text; 6 | 7 | const OnBoardingHeaderText({@required this.text}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Padding( 12 | padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), 13 | child: Text( 14 | text, 15 | style: TextStyle( 16 | color: Color(getColorHexFromStr(TEXT_COLOR_BLACK)), 17 | fontSize: MediaQuery.of(context).size.shortestSide * 0.07, 18 | letterSpacing: 0.8), 19 | softWrap: true, 20 | textAlign: TextAlign.left, 21 | ), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:resumepad/screen/on_boarding/on_boarding_page.dart'; 4 | import 'package:resumepad/screen/resume_maker/resume_maker_page.dart'; 5 | 6 | void main() => run(); 7 | 8 | Future run() async { 9 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light); 10 | SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]).then((_) { 11 | runApp(MyApp()); 12 | }); 13 | } 14 | 15 | class MyApp extends StatelessWidget { 16 | @override 17 | Widget build(BuildContext context) { 18 | return MaterialApp( 19 | debugShowCheckedModeBanner: false, 20 | title: 'ResumePad', 21 | theme: ThemeData.light(), 22 | home: OnBoardingPage(), 23 | initialRoute: '/', 24 | ); 25 | } 26 | } -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/screen/add_education/bloc/add_education_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:rxdart/rxdart.dart'; 3 | 4 | class AddEducationBloc { 5 | DateTime endDate = DateTime.now(); 6 | DateTime startDate = DateTime.now(); 7 | 8 | final _startDateBehaviorSubject = PublishSubject(); 9 | Stream get startDateStream => _startDateBehaviorSubject.stream; 10 | Sink get startDateSink => _startDateBehaviorSubject.sink; 11 | 12 | final _endDateBehaviorSubject = PublishSubject(); 13 | Stream get endDateStream => _endDateBehaviorSubject.stream; 14 | Sink get endDateSink => _endDateBehaviorSubject.sink; 15 | 16 | final _errorBehaviorSubject = PublishSubject(); 17 | Stream get errorStream => _errorBehaviorSubject.stream; 18 | Sink get errorSink => _errorBehaviorSubject.sink; 19 | 20 | dispose() { 21 | _startDateBehaviorSubject.close(); 22 | _endDateBehaviorSubject.close(); 23 | _errorBehaviorSubject.close(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/screen/add_project/bloc/add_project_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:rxdart/rxdart.dart'; 4 | 5 | class AddProjectBloc { 6 | DateTime endDate = DateTime.now(); 7 | DateTime startDate = DateTime.now(); 8 | 9 | final _startDateBehaviorSubject = PublishSubject(); 10 | Stream get startDateStream => _startDateBehaviorSubject.stream; 11 | Sink get startDateSink => _startDateBehaviorSubject.sink; 12 | 13 | final _endDateBehaviorSubject = PublishSubject(); 14 | Stream get endDateStream => _endDateBehaviorSubject.stream; 15 | Sink get endDateSink => _endDateBehaviorSubject.sink; 16 | 17 | final _errorBehaviorSubject = PublishSubject(); 18 | Stream get errorStream => _errorBehaviorSubject.stream; 19 | Sink get errorSink => _errorBehaviorSubject.sink; 20 | 21 | dispose() { 22 | _startDateBehaviorSubject.close(); 23 | _endDateBehaviorSubject.close(); 24 | _errorBehaviorSubject.close(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/screen/add_experience/bloc/add_experience_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:rxdart/rxdart.dart'; 4 | 5 | class AddExperienceBloc { 6 | DateTime endDate = DateTime.now(); 7 | DateTime startDate = DateTime.now(); 8 | 9 | final _startDateBehaviorSubject = PublishSubject(); 10 | Stream get startDateStream => _startDateBehaviorSubject.stream; 11 | Sink get startDateSink => _startDateBehaviorSubject.sink; 12 | 13 | final _endDateBehaviorSubject = PublishSubject(); 14 | Stream get endDateStream => _endDateBehaviorSubject.stream; 15 | Sink get endDateSink => _endDateBehaviorSubject.sink; 16 | 17 | final _errorBehaviorSubject = PublishSubject(); 18 | Stream get errorStream => _errorBehaviorSubject.stream; 19 | Sink get errorSink => _errorBehaviorSubject.sink; 20 | 21 | dispose() { 22 | _startDateBehaviorSubject.close(); 23 | _endDateBehaviorSubject.close(); 24 | _errorBehaviorSubject.close(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/utility/text_formatter/phone_text_formatter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | 3 | /// Format a phone number in format (###) ###-#### 4 | class PhoneTextInputFormatter extends TextInputFormatter { 5 | @override 6 | TextEditingValue formatEditUpdate( 7 | TextEditingValue oldValue, 8 | TextEditingValue newValue, 9 | ) { 10 | final int newTextLength = newValue.text.length; 11 | int selectionIndex = newValue.selection.end; 12 | int usedSubstringIndex = 0; 13 | final StringBuffer newText = StringBuffer(); 14 | if (newTextLength >= 1) { 15 | newText.write('('); 16 | if (newValue.selection.end >= 1) selectionIndex++; 17 | } 18 | if (newTextLength >= 4) { 19 | newText.write(newValue.text.substring(0, usedSubstringIndex = 3) + ') '); 20 | if (newValue.selection.end >= 3) selectionIndex += 2; 21 | } 22 | 23 | if (newTextLength >= usedSubstringIndex) { 24 | newText.write(newValue.text.substring(usedSubstringIndex)); 25 | } 26 | 27 | return TextEditingValue( 28 | text: newText.toString(), 29 | selection: TextSelection.collapsed(offset: selectionIndex), 30 | ); 31 | } 32 | } -------------------------------------------------------------------------------- /lib/utility/text_formatter/date_text_formatter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | 3 | class UsDateTextInputFormatter extends TextInputFormatter { 4 | @override 5 | TextEditingValue formatEditUpdate( 6 | TextEditingValue oldValue, 7 | TextEditingValue newValue, 8 | ) { 9 | final int newTextLength = newValue.text.length; 10 | int selectionIndex = newValue.selection.end; 11 | int usedSubstringIndex = 0; 12 | final StringBuffer newText = StringBuffer(); 13 | 14 | if (newTextLength >= 3) { 15 | newText.write(newValue.text.substring(0, usedSubstringIndex = 2) + '/'); 16 | if (newValue.selection.end >= 2) selectionIndex++; 17 | } 18 | 19 | if (newTextLength >= 5) { 20 | newText.write(newValue.text.substring(2, usedSubstringIndex = 4) + '/'); 21 | if (newValue.selection.end >= 4) selectionIndex++; 22 | } 23 | 24 | if (newTextLength >= usedSubstringIndex) { 25 | newText.write(newValue.text.substring(usedSubstringIndex)); 26 | } 27 | 28 | return TextEditingValue( 29 | text: newText.toString(), 30 | selection: TextSelection.collapsed(offset: selectionIndex), 31 | ); 32 | } 33 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Mayank Rai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/screen/preview_resume/preview_resume_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:flutter_full_pdf_viewer/full_pdf_viewer_scaffold.dart'; 4 | import 'package:wc_flutter_share/wc_flutter_share.dart'; 5 | 6 | class PDFScreen extends StatelessWidget { 7 | final String pathPDF; 8 | 9 | PDFScreen({@required this.pathPDF}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return PDFViewerScaffold( 14 | appBar: AppBar( 15 | title: Text("Preview"), 16 | actions: [ 17 | IconButton( 18 | icon: Icon(Icons.share), 19 | onPressed: _shareFile, 20 | ), 21 | ], 22 | ), 23 | path: pathPDF); 24 | } 25 | 26 | _shareFile() async { 27 | final ByteData bytes = await rootBundle.load(pathPDF); 28 | await WcFlutterShare.share( 29 | sharePopupTitle: 'share', 30 | fileName: '${pathPDF.substring(pathPDF.lastIndexOf("/") + 1)}', 31 | mimeType: 'application/pdf', 32 | bytesOfFile: bytes.buffer.asUint8List()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/screen/resume_maker/widget/page_indicator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class DotsIndicator extends StatelessWidget { 4 | final int dotsCount; 5 | final int position; 6 | 7 | const DotsIndicator({ 8 | Key key, 9 | @required this.dotsCount, 10 | this.position = 0, 11 | }) : assert(dotsCount != null && dotsCount > 0), 12 | assert(position != null && position >= 0), 13 | assert( 14 | position < dotsCount, 15 | "Position must be inferior than dotsCount", 16 | ), 17 | super(key: key); 18 | 19 | Widget _buildDot(int index) { 20 | final isCurrent = index == position; 21 | final size = isCurrent ? Size.square(8.0) : Size.square(4.0); 22 | 23 | return Container( 24 | width: size.width, 25 | height: size.height, 26 | margin: EdgeInsets.all(3.0), 27 | decoration: ShapeDecoration( 28 | color: isCurrent ? Colors.blue : Colors.grey, 29 | shape: isCurrent ? CircleBorder() : CircleBorder(), 30 | ), 31 | ); 32 | } 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | final dotsList = List.generate(dotsCount, _buildDot); 37 | return Column( 38 | mainAxisSize: MainAxisSize.min, 39 | children: dotsList, 40 | ); 41 | } 42 | } -------------------------------------------------------------------------------- /lib/widget/rounded_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class RoundedButton extends StatelessWidget { 4 | final Function onPressed; 5 | final String text; 6 | final bool isClickable; 7 | 8 | RoundedButton({@required this.onPressed, @required this.text, this.isClickable = true}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Padding( 13 | padding: EdgeInsets.symmetric(vertical: 12.0, horizontal: 24.0), 14 | child: Container( 15 | height: MediaQuery.of(context).size.shortestSide * 0.125, 16 | width: double.infinity, 17 | child: RaisedButton( 18 | shape: new RoundedRectangleBorder( 19 | borderRadius: new BorderRadius.circular(MediaQuery.of(context).size.shortestSide * 0.15)), 20 | padding: EdgeInsets.all(8.0), 21 | color: isClickable ? Theme.of(context).primaryColor : Theme.of(context).disabledColor, 22 | child: isClickable 23 | ? Text(text, 24 | style: TextStyle(color: Colors.white, fontSize: MediaQuery.of(context).size.shortestSide * 0.04)) 25 | : SizedBox( 26 | height: 16, 27 | width: 16, 28 | child: CircularProgressIndicator(), 29 | ), 30 | onPressed: isClickable ? onPressed : null, 31 | ), 32 | ), 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/utility/color_utility.dart: -------------------------------------------------------------------------------- 1 | int getColorHexFromStr(String colorStr) { 2 | colorStr = "FF" + colorStr; 3 | colorStr = colorStr.replaceAll("#", ""); 4 | int val = 0; 5 | int len = colorStr.length; 6 | for (int i = 0; i < len; i++) { 7 | int hexDigit = colorStr.codeUnitAt(i); 8 | if (hexDigit >= 48 && hexDigit <= 57) { 9 | val += (hexDigit - 48) * (1 << (4 * (len - 1 - i))); 10 | } else if (hexDigit >= 65 && hexDigit <= 70) { 11 | // A..F 12 | val += (hexDigit - 55) * (1 << (4 * (len - 1 - i))); 13 | } else if (hexDigit >= 97 && hexDigit <= 102) { 14 | // a..f 15 | val += (hexDigit - 87) * (1 << (4 * (len - 1 - i))); 16 | } else { 17 | throw new FormatException("An error occurred when converting a color"); 18 | } 19 | } 20 | return val; 21 | } 22 | 23 | const PRIMARY_COLOR = "#606BA1"; 24 | const TEXT_COLOR_BLACK = "#515151"; 25 | const ICONS_COLOR = "#9f9f9f"; 26 | 27 | const TEXT_COLOR_ORANGE = "#FFC400"; 28 | const COLOR_BACKGROUND = "#FAFAFA"; 29 | const DISABLED_GREY = "#D3D3D3"; 30 | const REMOVE_ITEM_COLOR = "#ff0000"; 31 | 32 | 33 | const BLACK = "#000000"; 34 | const WHITE = "#FFFFFF"; 35 | 36 | 37 | //Theme colors 38 | const THEME_COLOR_RED = "#6e2142"; 39 | const THEME_COLOR_BLUE = "#241663"; 40 | const THEME_COLOR_GREEN = "#207561"; 41 | const THEME_COLOR_YELLOW = "#560d0d"; 42 | const THEME_COLOR_DARK = "#252525"; 43 | 44 | 45 | 46 | const COLOR_NORMAL = "#3c763d"; 47 | const COLOR_DANGER = "#8B0000"; 48 | const COLOR_WARNING = "#FF8C00"; 49 | 50 | const COLOR_GOOD = "#008000"; 51 | const COLOR_BETTER = "#228B22"; 52 | const COLOR_BEST = "#006400"; -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # resumepad 2 | 3 | A new Flutter application for building resume. 4 | 5 | ## Screenshots 6 | ![Screenshot_2019-08-25-17-56-11-751_com mayandro resumepad](https://user-images.githubusercontent.com/16761273/63653018-bdb7a980-c767-11e9-80ea-64952e88c5e0.png) 7 | ![Screenshot_2019-08-25-17-57-21-374_com mayandro resumepad](https://user-images.githubusercontent.com/16761273/63653019-bdb7a980-c767-11e9-8701-3d80703c2f66.png) 8 | ![Screenshot_2019-08-25-17-57-26-357_com mayandro resumepad](https://user-images.githubusercontent.com/16761273/63653020-bdb7a980-c767-11e9-9970-cfa8fdd70882.png) 9 | ![Screenshot_2019-08-25-17-58-07-800_com mayandro resumepad](https://user-images.githubusercontent.com/16761273/63653021-be504000-c767-11e9-8969-f73bc60cede8.png) 10 | ![Screenshot_2019-08-25-17-58-19-684_com mayandro resumepad](https://user-images.githubusercontent.com/16761273/63653022-be504000-c767-11e9-9ba9-12b1170e104f.png) 11 | ![Screenshot_2019-08-25-17-59-44-808_com mayandro resumepad](https://user-images.githubusercontent.com/16761273/63653023-be504000-c767-11e9-9026-cd8f4b16e7a7.png) 12 | ![Screenshot_2019-08-25-18-07-45-859_com mayandro resumepad](https://user-images.githubusercontent.com/16761273/63653024-be504000-c767-11e9-9ab3-ffdea11b3612.png) 13 | ![Screenshot_2019-08-25-18-08-16-211_com mayandro resumepad](https://user-images.githubusercontent.com/16761273/63653025-bee8d680-c767-11e9-9e93-5de0bcb3641c.png) 14 | ![Screenshot_2019-08-25-18-09-31-751_com mayandro resumepad](https://user-images.githubusercontent.com/16761273/63653026-bee8d680-c767-11e9-9e89-6e626bfe1506.png) 15 | ![Screenshot_2019-08-25-18-09-52-577_com mayandro resumepad](https://user-images.githubusercontent.com/16761273/63653028-bee8d680-c767-11e9-9cba-1172fdb2bb76.png) 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | /build/ 31 | 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | **/android/key.properties 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Generated.xcconfig 62 | **/ios/Flutter/app.flx 63 | **/ios/Flutter/app.zip 64 | **/ios/Flutter/flutter_assets/ 65 | **/ios/ServiceDefinitions.json 66 | **/ios/Runner/GeneratedPluginRegistrant.* 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | -------------------------------------------------------------------------------- /lib/widget/date_picker_field.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:intl/intl.dart'; 3 | 4 | class DatePicker extends StatelessWidget { 5 | DatePicker({ 6 | this.labelText, 7 | this.errorText, 8 | Key key, 9 | DateTime dateTime, 10 | @required this.onChanged, 11 | }) : assert(onChanged != null), 12 | date = dateTime == null ? DateTime.now() : DateTime(dateTime.year, dateTime.month, dateTime.day), 13 | super(key: key); 14 | 15 | final String labelText; 16 | final String errorText; 17 | final DateTime date; 18 | final ValueChanged onChanged; 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return InkWell( 23 | onTap: (() => _showDatePicker(context)), 24 | child: InputDecorator( 25 | decoration: InputDecoration(labelText: labelText, errorText: errorText), 26 | child: Row( 27 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 28 | mainAxisSize: MainAxisSize.min, 29 | children: [ 30 | Text(DateFormat.yMMM().format(date)), 31 | Icon( 32 | Icons.arrow_drop_down, 33 | color: Theme.of(context).brightness == Brightness.light ? Colors.grey.shade700 : Colors.white70, 34 | ), 35 | ], 36 | ), 37 | ), 38 | ); 39 | } 40 | 41 | Future _showDatePicker(BuildContext context) async { 42 | DateTime dateTimePicked = await showDatePicker( 43 | context: context, 44 | initialDate: date, 45 | firstDate: date.subtract(const Duration(days: 20000)), 46 | lastDate: DateTime.now()); 47 | 48 | if (dateTimePicked != null) { 49 | onChanged(DateTime(dateTimePicked.year, dateTimePicked.month, dateTimePicked.day)); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/screen/resume_maker/widget/next_page_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:resumepad/screen/resume_maker/bloc/bloc_provider.dart'; 3 | 4 | class NextPageButton extends StatelessWidget { 5 | @override 6 | Widget build(BuildContext context) { 7 | final _bloc = ResumeMakerBlocProvider.of(context); 8 | return Positioned( 9 | bottom: 24, 10 | right: 24, 11 | child: StreamBuilder( 12 | stream: _bloc.mainPagerStream, 13 | builder: (BuildContext buildContext, AsyncSnapshot snapshot) { 14 | double pageOffset = snapshot.hasData ? snapshot.data : 0.0; 15 | return Transform.translate( 16 | offset: Offset(_getXOffset(pageOffset, MediaQuery.of(context).size.width), 0), 17 | child: ClipOval( 18 | child: StreamBuilder( 19 | stream: _bloc.nextButtonEnableStream, 20 | builder: (context, snapshotEnable) { 21 | var isEnable = snapshotEnable.hasData ? snapshotEnable.data : false; 22 | return Container( 23 | height: 32, 24 | width: 32, 25 | color: isEnable ? Theme.of(context).primaryColor : Colors.grey, 26 | child: IconButton( 27 | padding: EdgeInsets.all(4), 28 | color: Colors.white, 29 | onPressed: isEnable ? () => _bloc.navigateToNextPageIfPossible() : null, 30 | icon: const Icon(Icons.keyboard_arrow_down), 31 | )); 32 | }), 33 | ), 34 | ); 35 | }), 36 | ); 37 | } 38 | 39 | double _getXOffset(double pageOffset, double width) { 40 | double xOffset = 0; 41 | if (pageOffset > 8 && pageOffset <= 9) { 42 | xOffset = width * 0.4 * (pageOffset - 8); 43 | } 44 | return xOffset; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 13 | 20 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ResumePad 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | NSCameraUsageDescription 26 | Used to demonstrate image picker plugin 27 | NSMicrophoneUsageDescription 28 | Used to capture audio for image picker plugin 29 | NSPhotoLibraryUsageDescription 30 | Used to demonstrate image picker plugin 31 | UILaunchStoryboardName 32 | LaunchScreen 33 | UIMainStoryboardFile 34 | Main 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | UIViewControllerBasedStatusBarAppearance 49 | 50 | io.flutter.embedded_views_preview 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /lib/screen/resume_maker/widget/previous_page_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:resumepad/screen/resume_maker/bloc/bloc_provider.dart'; 3 | import 'package:resumepad/screen/resume_maker/bloc/resume_maker_bloc.dart'; 4 | 5 | class PreviousPageButton extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | final _bloc = ResumeMakerBlocProvider.of(context); 9 | return Positioned( 10 | bottom: 64, 11 | right: 24, 12 | child: StreamBuilder( 13 | stream: _bloc.mainPagerStream, 14 | builder: (BuildContext buildContext, AsyncSnapshot snapshot) { 15 | double pageOffset = snapshot.hasData ? snapshot.data : 0.0; 16 | return Transform.translate( 17 | offset: Offset(-_getXOffset(pageOffset, MediaQuery.of(context).size.width), 18 | _getYOffset(pageOffset, MediaQuery.of(context).size.height)), 19 | child: ClipOval( 20 | child: Container( 21 | height: 32, 22 | width: 32, 23 | color: Theme.of(context).primaryColor, 24 | child: IconButton( 25 | padding: EdgeInsets.all(4), 26 | color: Colors.white, 27 | onPressed: () { 28 | _bloc.navigateToPreviousPageIfPossible(); 29 | }, 30 | icon: const Icon(Icons.keyboard_arrow_up), 31 | )), 32 | ), 33 | ); 34 | }), 35 | ); 36 | } 37 | 38 | double _getXOffset(double pageOffset, double width) { 39 | double xOffset = -width * 0.1; 40 | if (pageOffset >= 0 && pageOffset <= 1) { 41 | xOffset = width * 0.4 * (pageOffset - 1); 42 | } else { 43 | xOffset = 0; 44 | } 45 | return xOffset; 46 | } 47 | 48 | double _getYOffset(double pageOffset, double height) { 49 | double yOffset = 0; 50 | if (pageOffset > 8 && pageOffset <= 9) { 51 | yOffset = 40 * (pageOffset - 8); 52 | } 53 | return yOffset; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/widget/custom_text_feild_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:resumepad/utility/color_utility.dart'; 4 | 5 | class CustomTextFieldForm extends StatelessWidget { 6 | final TextEditingController controller; 7 | final String hintText; 8 | final String helperText; 9 | final TextInputType textInputType; 10 | final Function validator; 11 | final int maxLines; 12 | final InputBorder inputBorder; 13 | final List inputFormatter; 14 | final int maxLength; 15 | final bool readOnly; 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return TextFormField( 20 | style: TextStyle( 21 | color: Color(getColorHexFromStr(TEXT_COLOR_BLACK)), 22 | fontSize: MediaQuery.of(context).size.shortestSide * 0.05, 23 | letterSpacing: 1.2), 24 | decoration: InputDecoration( 25 | border: inputBorder != null ? inputBorder : InputBorder.none, 26 | hintText: hintText, 27 | helperText: helperText, 28 | counterText: '', 29 | helperStyle: TextStyle( 30 | color: Color(getColorHexFromStr(TEXT_COLOR_BLACK)), 31 | fontSize: MediaQuery.of(context).size.shortestSide * 0.03, 32 | letterSpacing: 0.8), 33 | errorStyle: TextStyle(fontSize: MediaQuery.of(context).size.shortestSide * 0.03, letterSpacing: 0.8), 34 | ), 35 | keyboardType: textInputType == null ? TextInputType.text : textInputType, 36 | controller: controller, 37 | maxLines: maxLines != null ? maxLines : 1, 38 | validator: validator, 39 | inputFormatters: inputFormatter != null ? inputFormatter : [], 40 | maxLength: maxLength != null ? maxLength : null, 41 | readOnly: readOnly != null ? readOnly : false, 42 | ); 43 | } 44 | 45 | CustomTextFieldForm( 46 | {@required this.controller, 47 | @required this.helperText, 48 | @required this.hintText, 49 | this.textInputType, 50 | this.validator, 51 | this.maxLines, 52 | this.inputBorder, 53 | this.inputFormatter, 54 | this.maxLength, 55 | this.readOnly}); 56 | } 57 | -------------------------------------------------------------------------------- /lib/screen/on_boarding/on_boarding_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:resumepad/screen/resume_maker/resume_maker_page.dart'; 3 | import 'package:resumepad/utility/fade_route_builder.dart'; 4 | import 'package:resumepad/widget/rounded_button.dart'; 5 | 6 | class OnBoardingPage extends StatelessWidget { 7 | @override 8 | Widget build(BuildContext context) { 9 | return Scaffold( 10 | backgroundColor: Colors.white, 11 | body: SafeArea( 12 | child: Column( 13 | children: [ 14 | buildTitle(context), 15 | buildImage(context), 16 | buildMessage(context), 17 | buildButton(context), 18 | ], 19 | )), 20 | ); 21 | } 22 | 23 | buildTitle(BuildContext context) { 24 | return Padding( 25 | padding: const EdgeInsets.all(24.0), 26 | child: Text( 27 | 'RESUMEPAD', 28 | style: 29 | TextStyle(color: Theme.of(context).primaryColor, fontSize: MediaQuery.of(context).size.shortestSide * 0.1), 30 | textAlign: TextAlign.center, 31 | ), 32 | ); 33 | } 34 | 35 | buildButton(BuildContext context) { 36 | return Padding( 37 | padding: const EdgeInsets.all(24.0), 38 | child: RoundedButton( 39 | text: 'Create', 40 | onPressed: () => Navigator.of(context).pushReplacement(FadeRouteBuilder(page: ResumeMakerPage())), 41 | ), 42 | ); 43 | } 44 | 45 | buildMessage(BuildContext context) { 46 | return Padding( 47 | padding: const EdgeInsets.all(24.0), 48 | child: Text( 49 | 'Your free resume maker.\nNo adds and easy to use', 50 | style: TextStyle(color: Colors.black87, fontSize: MediaQuery.of(context).size.shortestSide * 0.05), 51 | textAlign: TextAlign.center, 52 | ), 53 | ); 54 | } 55 | 56 | buildImage(BuildContext context) { 57 | return SizedBox( 58 | height: MediaQuery.of(context).size.height * 0.4, 59 | width: double.infinity, 60 | child: Padding( 61 | padding: EdgeInsets.symmetric(vertical: 12.0, horizontal: 24.0), 62 | child: Image.asset('assets/cv.png'), 63 | ), 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | pods_ary = [] 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) { |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | pods_ary.push({:name => podname, :path => podpath}); 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | } 32 | return pods_ary 33 | end 34 | 35 | target 'Runner' do 36 | use_frameworks! 37 | 38 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 39 | # referring to absolute paths on developers' machines. 40 | system('rm -rf .symlinks') 41 | system('mkdir -p .symlinks/plugins') 42 | 43 | # Flutter Pods 44 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 45 | if generated_xcode_build_settings.empty? 46 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first." 47 | end 48 | generated_xcode_build_settings.map { |p| 49 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 50 | symlink = File.join('.symlinks', 'flutter') 51 | File.symlink(File.dirname(p[:path]), symlink) 52 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 53 | end 54 | } 55 | 56 | # Plugin Pods 57 | plugin_pods = parse_KV_file('../.flutter-plugins') 58 | plugin_pods.map { |p| 59 | symlink = File.join('.symlinks', 'plugins', p[:name]) 60 | File.symlink(p[:path], symlink) 61 | pod p[:name], :path => File.join(symlink, 'ios') 62 | } 63 | end 64 | 65 | # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. 66 | install! 'cocoapods', :disable_input_output_paths => true 67 | 68 | post_install do |installer| 69 | installer.pods_project.targets.each do |target| 70 | target.build_configurations.each do |config| 71 | config.build_settings['ENABLE_BITCODE'] = 'NO' 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | def keystoreProperties = new Properties() 29 | def keystorePropertiesFile = rootProject.file('key.properties') 30 | if (keystorePropertiesFile.exists()) { 31 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 32 | } 33 | 34 | android { 35 | compileSdkVersion 28 36 | 37 | sourceSets { 38 | main.java.srcDirs += 'src/main/kotlin' 39 | } 40 | 41 | lintOptions { 42 | disable 'InvalidPackage' 43 | } 44 | 45 | defaultConfig { 46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 47 | applicationId "com.mayandro.resumepad" 48 | minSdkVersion 21 49 | targetSdkVersion 28 50 | versionCode flutterVersionCode.toInteger() 51 | versionName flutterVersionName 52 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 53 | } 54 | 55 | signingConfigs { 56 | release { 57 | keyAlias keystoreProperties['keyAlias'] 58 | keyPassword keystoreProperties['keyPassword'] 59 | storeFile file(keystoreProperties['storeFile']) 60 | storePassword keystoreProperties['storePassword'] 61 | } 62 | } 63 | buildTypes { 64 | release { 65 | signingConfig signingConfigs.release 66 | } 67 | } 68 | } 69 | 70 | flutter { 71 | source '../..' 72 | } 73 | 74 | dependencies { 75 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 76 | testImplementation 'junit:junit:4.12' 77 | androidTestImplementation 'androidx.test:runner:1.1.1' 78 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 79 | } 80 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_full_pdf_viewer (1.0.1): 4 | - Flutter 5 | - flutter_lottie (0.0.1): 6 | - Flutter 7 | - lottie-ios 8 | - image_cropper (0.0.1): 9 | - Flutter 10 | - TOCropViewController (~> 2.5.0) 11 | - image_picker (0.0.1): 12 | - Flutter 13 | - lottie-ios (2.5.3) 14 | - path_provider (0.0.1): 15 | - Flutter 16 | - shared_preferences (0.0.1): 17 | - Flutter 18 | - TOCropViewController (2.5.1) 19 | - url_launcher (0.0.1): 20 | - Flutter 21 | - wc_flutter_share (0.0.1): 22 | - Flutter 23 | 24 | DEPENDENCIES: 25 | - Flutter (from `.symlinks/flutter/ios`) 26 | - flutter_full_pdf_viewer (from `.symlinks/plugins/flutter_full_pdf_viewer/ios`) 27 | - flutter_lottie (from `.symlinks/plugins/flutter_lottie/ios`) 28 | - image_cropper (from `.symlinks/plugins/image_cropper/ios`) 29 | - image_picker (from `.symlinks/plugins/image_picker/ios`) 30 | - path_provider (from `.symlinks/plugins/path_provider/ios`) 31 | - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) 32 | - url_launcher (from `.symlinks/plugins/url_launcher/ios`) 33 | - wc_flutter_share (from `.symlinks/plugins/wc_flutter_share/ios`) 34 | 35 | SPEC REPOS: 36 | https://github.com/cocoapods/specs.git: 37 | - lottie-ios 38 | - TOCropViewController 39 | 40 | EXTERNAL SOURCES: 41 | Flutter: 42 | :path: ".symlinks/flutter/ios" 43 | flutter_full_pdf_viewer: 44 | :path: ".symlinks/plugins/flutter_full_pdf_viewer/ios" 45 | flutter_lottie: 46 | :path: ".symlinks/plugins/flutter_lottie/ios" 47 | image_cropper: 48 | :path: ".symlinks/plugins/image_cropper/ios" 49 | image_picker: 50 | :path: ".symlinks/plugins/image_picker/ios" 51 | path_provider: 52 | :path: ".symlinks/plugins/path_provider/ios" 53 | shared_preferences: 54 | :path: ".symlinks/plugins/shared_preferences/ios" 55 | url_launcher: 56 | :path: ".symlinks/plugins/url_launcher/ios" 57 | wc_flutter_share: 58 | :path: ".symlinks/plugins/wc_flutter_share/ios" 59 | 60 | SPEC CHECKSUMS: 61 | Flutter: 58dd7d1b27887414a370fcccb9e645c08ffd7a6a 62 | flutter_full_pdf_viewer: 8a6723fa44d3e421520b70ca95fa38155a68eccf 63 | flutter_lottie: c1fd7e92e771209f8b623119646c15201c5e8c87 64 | image_cropper: f25bdfbf1ef970c9a0a0f78306fb6a0aaac7229e 65 | image_picker: 16e5fec1fbc87fd3b297c53e4048521eaf17cd06 66 | lottie-ios: a50d5c0160425cd4b01b852bb9578963e6d92d31 67 | path_provider: f96fff6166a8867510d2c25fdcc346327cc4b259 68 | shared_preferences: 1feebfa37bb57264736e16865e7ffae7fc99b523 69 | TOCropViewController: 00dc36c4e4a0f4a45efa91adbf22df8a4fae01d3 70 | url_launcher: 0067ddb8f10d36786672aa0722a21717dba3a298 71 | wc_flutter_share: acdfe1a80d72f39da40378151fe50d5d86f15cbf 72 | 73 | PODFILE CHECKSUM: b6a0a141693093b304368d08511b46cf3d1d0ac5 74 | 75 | COCOAPODS: 1.7.5 76 | -------------------------------------------------------------------------------- /lib/screen/resume_maker/widget/user_name_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:resumepad/screen/resume_maker/bloc/bloc_provider.dart'; 3 | import 'package:resumepad/utility/color_utility.dart'; 4 | 5 | class UserNameWidget extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | final _bloc = ResumeMakerBlocProvider.of(context); 9 | return Positioned( 10 | top: MediaQuery.of(context).size.height * 0.1, 11 | right: 0, 12 | left: MediaQuery.of(context).size.width * 0.3, 13 | child: StreamBuilder( 14 | stream: _bloc.mainPagerStream, 15 | builder: (BuildContext buildContext, AsyncSnapshot snapshot) { 16 | double pageOffset = snapshot.hasData ? snapshot.data : 0.0; 17 | return Transform.scale( 18 | scale: _getScaleOffset(pageOffset), 19 | child: Opacity( 20 | opacity: _getOpactity(pageOffset), 21 | child: Column( 22 | mainAxisAlignment: MainAxisAlignment.start, 23 | crossAxisAlignment: CrossAxisAlignment.start, 24 | children: [ 25 | Text( 26 | _bloc.profile != null ? _bloc.profile.name : '', 27 | style: TextStyle( 28 | color: Color(getColorHexFromStr(TEXT_COLOR_BLACK)), 29 | fontSize: MediaQuery.of(context).size.shortestSide * 0.06, 30 | letterSpacing: 0.8), 31 | softWrap: true, 32 | textAlign: TextAlign.left, 33 | ), 34 | Text( 35 | _bloc.profile != null ? _bloc.profile.designation : '', 36 | style: TextStyle( 37 | color: Color(getColorHexFromStr(TEXT_COLOR_BLACK)), 38 | fontSize: MediaQuery.of(context).size.shortestSide * 0.04, 39 | letterSpacing: 0.8), 40 | softWrap: true, 41 | textAlign: TextAlign.left, 42 | ), 43 | ], 44 | )), 45 | ); 46 | }), 47 | ); 48 | } 49 | 50 | double _getOpactity(double pageOffset) { 51 | if (pageOffset < 1) { 52 | return 0; 53 | } else if (pageOffset >= 1 && pageOffset < 2) { 54 | return pageOffset - 1; 55 | } else if (pageOffset >= 8 && pageOffset <= 9) { 56 | return 9 - pageOffset; 57 | } else { 58 | return 1; 59 | } 60 | } 61 | 62 | double _getScaleOffset(double pageOffset) { 63 | if (pageOffset < 1) { 64 | return 0; 65 | } else if (pageOffset >= 1 && pageOffset < 2) { 66 | return pageOffset - 1; 67 | } else if (pageOffset >= 8 && pageOffset <= 9) { 68 | return 9 - pageOffset; 69 | } else { 70 | return 1; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/screen/resume_maker/tabs/all_done_tab.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:path_provider/path_provider.dart'; 5 | import 'package:resumepad/screen/preview_resume/preview_resume_page.dart'; 6 | import 'package:resumepad/screen/resume_maker/bloc/bloc_provider.dart'; 7 | import 'package:resumepad/utility/color_utility.dart'; 8 | import 'package:resumepad/widget/rounded_button.dart'; 9 | 10 | class AllDoneTab extends StatelessWidget { 11 | @override 12 | Widget build(BuildContext context) { 13 | return Container( 14 | margin: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.45), 15 | child: _buildContent(context), 16 | ); 17 | } 18 | 19 | _buildContent(BuildContext context) { 20 | final _bloc = ResumeMakerBlocProvider.of(context); 21 | return Column( 22 | children: [ 23 | Padding( 24 | padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), 25 | child: Text( 26 | 'You are finished with data filling.\n\nClick on preview to see your resume.', 27 | style: TextStyle( 28 | color: Color(getColorHexFromStr(TEXT_COLOR_BLACK)), 29 | fontSize: MediaQuery.of(context).size.shortestSide * 0.05, 30 | letterSpacing: 0.8), 31 | softWrap: true, 32 | textAlign: TextAlign.center, 33 | ), 34 | ), 35 | Spacer(), 36 | StreamBuilder( 37 | stream: _bloc.previewButtonVisibilityStream, 38 | builder: (context, snapshot) { 39 | var isButtonVisible = snapshot.hasData ? snapshot.data : true; 40 | 41 | return isButtonVisible 42 | ? RoundedButton( 43 | text: 'See Preview', 44 | onPressed: () => _createFile(context), 45 | ) 46 | : Column( 47 | children: [ 48 | CircularProgressIndicator(), 49 | Text( 50 | 'Preparing your resume ...', 51 | style: TextStyle(fontSize: MediaQuery.of(context).size.shortestSide * 0.04), 52 | ) 53 | ], 54 | ); 55 | }), 56 | Spacer( 57 | flex: 2, 58 | ), 59 | ], 60 | ); 61 | } 62 | 63 | _createFile(BuildContext context) async { 64 | final _bloc = ResumeMakerBlocProvider.of(context); 65 | _bloc.previewButtonVisibilitySink.add(false); 66 | _bloc.createPdf().then((val) { 67 | _previewPdfFile(context, _bloc.profile.name); 68 | }); 69 | } 70 | 71 | _previewPdfFile(BuildContext context, String name) { 72 | getApplicationDocumentsDirectory().then((value) { 73 | final _bloc = ResumeMakerBlocProvider.of(context); 74 | _bloc.previewButtonVisibilitySink.add(true); 75 | Navigator.push( 76 | context, 77 | MaterialPageRoute(builder: (context) => PDFScreen(pathPDF: '${value.path}/${name.replaceAll(' ', '_')}.pdf')), 78 | ); 79 | }); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: resumepad 2 | description: A new Flutter application for building resume. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.0.0+1 15 | 16 | environment: 17 | sdk: ">=2.1.0 <3.0.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | 23 | # The following adds the Cupertino Icons font to your application. 24 | # Use with the CupertinoIcons class for iOS style icons. 25 | cupertino_icons: ^0.1.2 26 | pdf: ^1.3.17 27 | path_provider: ^1.2.0 28 | rxdart: ^0.22.0 29 | intl: ^0.15.8 30 | image_picker: ^0.6.1+3 31 | image_cropper: ^1.0.2 32 | flutter_full_pdf_viewer: ^1.0.4 33 | wc_flutter_share: ^0.1.1 34 | 35 | 36 | dev_dependencies: 37 | flutter_test: 38 | sdk: flutter 39 | 40 | flutter_launcher_icons: 41 | 42 | flutter_icons: 43 | image_path: "assets/icons.png" 44 | android: true 45 | ios: true 46 | 47 | # For information on the generic Dart part of this file, see the 48 | # following page: https://dart.dev/tools/pub/pubspec 49 | 50 | # The following section is specific to Flutter. 51 | flutter: 52 | 53 | # The following line ensures that the Material Icons font is 54 | # included with your application, so that you can use the icons in 55 | # the material Icons class. 56 | uses-material-design: true 57 | 58 | # To add assets to your application, add an assets section, like this: 59 | assets: 60 | - assets/in.png 61 | - assets/location.png 62 | - assets/mail.png 63 | - assets/mobile.png 64 | - animations/resume.json 65 | - assets/cv.png 66 | 67 | # An image asset can refer to one or more resolution-specific "variants", see 68 | # https://flutter.dev/assets-and-images/#resolution-aware. 69 | 70 | # For details regarding adding assets from package dependencies, see 71 | # https://flutter.dev/assets-and-images/#from-packages 72 | 73 | # To add custom fonts to your application, add a fonts section here, 74 | # in this "flutter" section. Each entry in this list should have a 75 | # "family" key with the font family name, and a "fonts" key with a 76 | # list giving the asset and other descriptors for the font. For 77 | # example: 78 | # fonts: 79 | # - family: Schyler 80 | # fonts: 81 | # - asset: fonts/Schyler-Regular.ttf 82 | # - asset: fonts/Schyler-Italic.ttf 83 | # style: italic 84 | # - family: Trajan Pro 85 | # fonts: 86 | # - asset: fonts/TrajanPro.ttf 87 | # - asset: fonts/TrajanPro_Bold.ttf 88 | # weight: 700 89 | # 90 | # For details regarding fonts from package dependencies, 91 | # see https://flutter.dev/custom-fonts/#from-packages 92 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /lib/screen/resume_maker/tabs/profile_summary_tab.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:resumepad/screen/resume_maker/bloc/bloc_provider.dart'; 3 | import 'package:resumepad/screen/resume_maker/widget/onboarding_header_text.dart'; 4 | import 'package:resumepad/widget/custom_text_feild_form.dart'; 5 | import 'package:resumepad/widget/rounded_button.dart'; 6 | 7 | class ProfileSummaryTab extends StatefulWidget { 8 | @override 9 | _ProfileSummaryTabState createState() => _ProfileSummaryTabState(); 10 | } 11 | 12 | class _ProfileSummaryTabState extends State { 13 | TextEditingController _summaryController; 14 | final _formKey = GlobalKey(); 15 | 16 | @override 17 | void initState() { 18 | super.initState(); 19 | _summaryController = TextEditingController(); 20 | } 21 | 22 | @override 23 | void didChangeDependencies() { 24 | super.didChangeDependencies(); 25 | final _bloc = ResumeMakerBlocProvider.of(context); 26 | _summaryController.text = _bloc.profileSummary != null ? _bloc.profileSummary : ''; 27 | _summaryController..addListener(_onSummaryChange); 28 | } 29 | 30 | void _onSummaryChange() { 31 | final _bloc = ResumeMakerBlocProvider.of(context); 32 | if (_bloc.profileSummary != null && _summaryController.text != _bloc.profileSummary) { 33 | _bloc.saveProfileSummaryButtonEnableSink.add(true); 34 | _bloc.nextButtonEnableSink.add(false); 35 | } 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return Container( 41 | margin: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.18), 42 | child: buildForm(), 43 | ); 44 | } 45 | 46 | Widget buildForm() { 47 | final _bloc = ResumeMakerBlocProvider.of(context); 48 | return SingleChildScrollView( 49 | child: Form( 50 | key: _formKey, 51 | child: Padding( 52 | padding: const EdgeInsets.all(8.0), 53 | child: Column( 54 | mainAxisAlignment: MainAxisAlignment.start, 55 | crossAxisAlignment: CrossAxisAlignment.start, 56 | children: [ 57 | OnBoardingHeaderText(text: 'Summary'), 58 | Padding( 59 | padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), 60 | child: CustomTextFieldForm( 61 | controller: _summaryController, 62 | helperText: 'Write few words about you...', 63 | hintText: 'Your summary', 64 | maxLines: 10, 65 | inputBorder: OutlineInputBorder(), 66 | validator: (val) => val.length == 0 ? 'Empty summary' : val.length < 2 ? 'Invalid summary' : null, 67 | ), 68 | ), 69 | SizedBox( 70 | height: 12, 71 | ), 72 | StreamBuilder( 73 | stream: _bloc.saveProfileSummaryButtonEnableStream, 74 | builder: (context, snapshot) { 75 | bool isEnable = snapshot.hasData ? snapshot.data : _bloc.profileSummary != null ? false : true; 76 | return RoundedButton( 77 | text: 'Save', 78 | onPressed: isEnable ? _onAddProfileSummary : null, 79 | ); 80 | }), 81 | ], 82 | ), 83 | ), 84 | ), 85 | ); 86 | } 87 | 88 | void _onAddProfileSummary() { 89 | FocusScope.of(context).requestFocus(new FocusNode()); 90 | final _bloc = ResumeMakerBlocProvider.of(context); 91 | if (_formKey.currentState.validate()) { 92 | _formKey.currentState.save(); 93 | _bloc.profileSummary = _summaryController.text; 94 | _bloc.saveProfileSummaryButtonEnableSink.add(false); 95 | _bloc.nextButtonEnableSink.add(true); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/screen/add_language/add_language_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:resumepad/model/resume_model.dart'; 3 | import 'package:resumepad/screen/add_language/bloc/add_language_bloc.dart'; 4 | import 'package:resumepad/widget/custom_text_feild_form.dart'; 5 | import 'package:resumepad/widget/rounded_button.dart'; 6 | 7 | import 'bloc/bloc_provider.dart'; 8 | 9 | class AddLanguageDialog extends StatefulWidget { 10 | final Language language; 11 | 12 | AddLanguageDialog({this.language}); 13 | 14 | @override 15 | _AddLanguageDialogState createState() => _AddLanguageDialogState(); 16 | } 17 | 18 | class _AddLanguageDialogState extends State { 19 | TextEditingController _nameController; 20 | final _formKey = GlobalKey(); 21 | AddLanguageBloc _bloc; 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | _bloc = AddLanguageBloc(); 27 | _nameController = TextEditingController(text: widget.language != null ? widget.language.name : ''); 28 | } 29 | 30 | @override 31 | void dispose() { 32 | _nameController.dispose(); 33 | _bloc.dispose(); 34 | super.dispose(); 35 | } 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | return AddLanguageBlocProvider( 40 | bloc: _bloc, 41 | child: Scaffold( 42 | body: _buildForm(), 43 | appBar: AppBar( 44 | backgroundColor: Colors.transparent, 45 | elevation: 0, 46 | iconTheme: IconThemeData(color: Colors.black), 47 | title: Text( 48 | 'Add Language', 49 | style: TextStyle( 50 | color: Colors.black, 51 | ), 52 | ), 53 | ), 54 | ), 55 | ); 56 | } 57 | 58 | Widget _buildForm() { 59 | return SingleChildScrollView( 60 | child: Form( 61 | key: _formKey, 62 | child: Padding( 63 | padding: const EdgeInsets.all(24.0), 64 | child: Column( 65 | mainAxisSize: MainAxisSize.min, 66 | mainAxisAlignment: MainAxisAlignment.start, 67 | crossAxisAlignment: CrossAxisAlignment.start, 68 | children: [ 69 | CustomTextFieldForm( 70 | controller: _nameController, 71 | hintText: 'You speak ...', 72 | helperText: 'Language name', 73 | validator: (val) => val.length == 0 ? 'Empty name' : null, 74 | ), 75 | SizedBox(height: 24.0), 76 | StreamBuilder( 77 | stream: _bloc.levelSliderStream, 78 | builder: (context, snapshot) { 79 | double level = snapshot.hasData ? snapshot.data : 0; 80 | return Slider( 81 | label: 'Level', 82 | value: level, 83 | onChanged: (value) { 84 | _bloc.levelSliderSink.add(value); 85 | _bloc.level = value; 86 | }); 87 | }), 88 | Align( 89 | alignment: Alignment.center, 90 | child: StreamBuilder( 91 | stream: _bloc.levelSliderStream, 92 | builder: (context, snapshot) { 93 | double level = snapshot.hasData ? snapshot.data : 0; 94 | return Text( 95 | _getLanguageLevel(level * 100), 96 | textAlign: TextAlign.center, 97 | ); 98 | }), 99 | ), 100 | SizedBox(height: 24.0), 101 | RoundedButton( 102 | text: 'Add', 103 | onPressed: onAddLanguage, 104 | ), 105 | ], 106 | ), 107 | ), 108 | ), 109 | ); 110 | } 111 | 112 | void onAddLanguage() { 113 | FocusScope.of(context).requestFocus(new FocusNode()); 114 | if (_formKey.currentState.validate()) { 115 | _formKey.currentState.save(); 116 | 117 | Navigator.of(context).pop( 118 | Language( 119 | name: _nameController.text, 120 | level: _getLanguageLevel(_bloc.level * 100), 121 | ), 122 | ); 123 | } 124 | } 125 | 126 | String _getLanguageLevel(double level) { 127 | if (level < 25) { 128 | return 'Beginner Level'; 129 | } 130 | 131 | if (level < 50) { 132 | return 'Conversational Level'; 133 | } 134 | 135 | if (level < 75) { 136 | return 'Business Level'; 137 | } 138 | 139 | return 'Fluent Level'; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /lib/screen/resume_maker/resume_maker_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:resumepad/screen/resume_maker/tabs/all_done_tab.dart'; 3 | import 'package:resumepad/screen/resume_maker/tabs/contact_tab.dart'; 4 | import 'package:resumepad/screen/resume_maker/tabs/education_tab.dart'; 5 | import 'package:resumepad/screen/resume_maker/tabs/experience_tab.dart'; 6 | import 'package:resumepad/screen/resume_maker/tabs/language_tab.dart'; 7 | import 'package:resumepad/screen/resume_maker/tabs/profile_summary_tab.dart'; 8 | import 'package:resumepad/screen/resume_maker/tabs/profile_tab.dart'; 9 | import 'package:resumepad/screen/resume_maker/tabs/projects_tab.dart'; 10 | import 'package:resumepad/screen/resume_maker/tabs/reference_tab.dart'; 11 | import 'package:resumepad/screen/resume_maker/tabs/skills_tab.dart'; 12 | import 'package:resumepad/screen/resume_maker/widget/page_indicator.dart'; 13 | import 'package:resumepad/screen/resume_maker/widget/previous_page_button.dart'; 14 | import 'package:resumepad/screen/resume_maker/widget/user_name_widget.dart'; 15 | import 'package:resumepad/screen/resume_maker/widget/user_picture_widget.dart'; 16 | import 'package:resumepad/screen/resume_maker/widget/next_page_button.dart'; 17 | import 'package:resumepad/utility/constants.dart'; 18 | 19 | import 'bloc/bloc_provider.dart'; 20 | import 'bloc/resume_maker_bloc.dart'; 21 | 22 | const _submitAnimationTime = 300; 23 | 24 | class ResumeMakerPage extends StatefulWidget { 25 | @override 26 | _ResumeMakerPageState createState() => _ResumeMakerPageState(); 27 | } 28 | 29 | class _ResumeMakerPageState extends State with TickerProviderStateMixin { 30 | AnimationController _animationController; 31 | ResumeMakerBloc _bloc; 32 | PageController _pageController; 33 | 34 | Future _navigateToPage(int page) async { 35 | _pageController.animateToPage(page, duration: Duration(milliseconds: _submitAnimationTime), curve: Curves.linear); 36 | _bloc.currentPage = page; 37 | } 38 | 39 | @override 40 | void initState() { 41 | super.initState(); 42 | _animationController = AnimationController( 43 | vsync: this, 44 | lowerBound: 0.5, 45 | duration: Duration(seconds: 3), 46 | )..repeat(); 47 | _bloc = ResumeMakerBloc(); 48 | _bloc.pageNavigationStream.listen(_navigateToPage); 49 | _pageController = PageController( 50 | initialPage: 0, 51 | viewportFraction: 1.0, 52 | ); 53 | } 54 | 55 | @override 56 | void dispose() { 57 | _animationController.dispose(); 58 | _bloc.dispose(); 59 | _pageController.dispose(); 60 | super.dispose(); 61 | } 62 | 63 | @override 64 | Widget build(BuildContext context) { 65 | return ResumeMakerBlocProvider( 66 | bloc: _bloc, 67 | child: WillPopScope( 68 | onWillPop: _onWillPop, 69 | child: Scaffold( 70 | body: Stack( 71 | children: [ 72 | _createPager(), 73 | UserPictureWidget(_animationController), 74 | NextPageButton(), 75 | PreviousPageButton(), 76 | UserNameWidget(), 77 | StreamBuilder( 78 | stream: _bloc.pageNavigationStream, 79 | builder: (context, snapshot) { 80 | return Positioned( 81 | right: 16, 82 | top: 36, 83 | child: DotsIndicator( 84 | dotsCount: 10, 85 | position: snapshot.hasData ? snapshot.data : 0, 86 | ), 87 | ); 88 | }) 89 | ], 90 | ), 91 | ), 92 | ), 93 | ); 94 | } 95 | 96 | _createPager() { 97 | return NotificationListener( 98 | onNotification: (ScrollNotification notification) { 99 | if (notification is ScrollUpdateNotification) { 100 | _bloc.mainPagerSink.add(_pageController.page); 101 | } 102 | return true; 103 | }, 104 | child: PageView( 105 | onPageChanged: (pos) { 106 | _bloc.pageNavigationSink.add(pos); 107 | _bloc.currentPage = pos; 108 | }, 109 | physics: NeverScrollableScrollPhysics(), 110 | scrollDirection: Axis.vertical, 111 | controller: _pageController, 112 | children: [ 113 | ProfileTab(), 114 | ProfileSummaryTab(), 115 | ContactTab(), 116 | EducationTab(), 117 | ExperienceTab(), 118 | ProjectTab(), 119 | SkillTab(), 120 | ReferenceTab(), 121 | LanguageTab(), 122 | AllDoneTab(), 123 | ], 124 | ), 125 | ); 126 | } 127 | 128 | Future _onWillPop() async { 129 | return true; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /lib/screen/add_reference/add_reference_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:resumepad/model/resume_model.dart'; 3 | import 'package:resumepad/widget/custom_text_feild_form.dart'; 4 | import 'package:resumepad/widget/rounded_button.dart'; 5 | 6 | class AddReferenceDialog extends StatefulWidget { 7 | final Reference reference; 8 | 9 | AddReferenceDialog({this.reference}); 10 | 11 | @override 12 | _AddReferenceDialogState createState() => _AddReferenceDialogState(); 13 | } 14 | 15 | class _AddReferenceDialogState extends State { 16 | TextEditingController _nameController; 17 | TextEditingController _designationController; 18 | TextEditingController _emailController; 19 | TextEditingController _companyController; 20 | final _formKey = GlobalKey(); 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | _nameController = TextEditingController(text: widget.reference != null ? widget.reference.name : ''); 26 | _emailController = TextEditingController(text: widget.reference != null ? widget.reference.email : ''); 27 | _designationController = TextEditingController(text: widget.reference != null ? widget.reference.designation : ''); 28 | _companyController = TextEditingController(text: widget.reference != null ? widget.reference.company : ''); 29 | } 30 | 31 | @override 32 | void dispose() { 33 | _nameController.dispose(); 34 | _emailController.dispose(); 35 | _designationController.dispose(); 36 | _companyController.dispose(); 37 | super.dispose(); 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | return Scaffold( 43 | body: _buildForm(), 44 | appBar: AppBar( 45 | backgroundColor: Colors.transparent, 46 | elevation: 0, 47 | iconTheme: IconThemeData(color: Colors.black), 48 | title: Text( 49 | 'Add Reference', 50 | style: TextStyle( 51 | color: Colors.black, 52 | ), 53 | ), 54 | ), 55 | ); 56 | } 57 | 58 | Widget _buildForm() { 59 | return SingleChildScrollView( 60 | child: Form( 61 | key: _formKey, 62 | child: Padding( 63 | padding: const EdgeInsets.all(24.0), 64 | child: Column( 65 | mainAxisSize: MainAxisSize.min, 66 | mainAxisAlignment: MainAxisAlignment.start, 67 | crossAxisAlignment: CrossAxisAlignment.start, 68 | children: [ 69 | CustomTextFieldForm( 70 | controller: _nameController, 71 | hintText: 'Reference person name ...', 72 | helperText: 'Name', 73 | validator: (val) => val.length == 0 ? 'Empty name' : val.length < 2 ? 'Invalid name' : null, 74 | ), 75 | SizedBox(height: 12.0), 76 | CustomTextFieldForm( 77 | controller: _designationController, 78 | hintText: 'Designation ...', 79 | helperText: 'Designation', 80 | validator: (val) => val.length == 0 ? 'Empty designation' : null, 81 | ), 82 | SizedBox(height: 12.0), 83 | CustomTextFieldForm( 84 | controller: _companyController, 85 | hintText: 'Company name ...', 86 | helperText: 'Company', 87 | validator: (val) => val.length == 0 ? 'Empty company name' : null, 88 | ), 89 | SizedBox(height: 12.0), 90 | CustomTextFieldForm( 91 | controller: _emailController, 92 | hintText: 'Email?', 93 | helperText: 'Email', 94 | textInputType: TextInputType.emailAddress, 95 | validator: (val) { 96 | Pattern pattern = 97 | r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$'; 98 | RegExp regex = new RegExp(pattern); 99 | if (!regex.hasMatch(val)) 100 | return 'Enter Valid Email'; 101 | else 102 | return null; 103 | }, 104 | ), 105 | SizedBox(height: 24.0), 106 | RoundedButton( 107 | text: 'Add', 108 | onPressed: onAddReference, 109 | ), 110 | ], 111 | ), 112 | ), 113 | ), 114 | ); 115 | } 116 | 117 | void onAddReference() { 118 | FocusScope.of(context).requestFocus(new FocusNode()); 119 | if (_formKey.currentState.validate()) { 120 | _formKey.currentState.save(); 121 | 122 | Navigator.of(context).pop( 123 | Reference( 124 | designation: _designationController.text, 125 | name: _nameController.text, 126 | email: _emailController.text, 127 | company: _companyController.text, 128 | ), 129 | ); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /lib/screen/resume_maker/tabs/skills_tab.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:resumepad/screen/resume_maker/bloc/bloc_provider.dart'; 3 | import 'package:resumepad/screen/resume_maker/widget/onboarding_header_text.dart'; 4 | import 'package:resumepad/widget/custom_text_feild_form.dart'; 5 | 6 | class SkillTab extends StatefulWidget { 7 | @override 8 | _SkillTabState createState() => _SkillTabState(); 9 | } 10 | 11 | class _SkillTabState extends State { 12 | final TextEditingController _skillController = TextEditingController(); 13 | final _formKey = GlobalKey(); 14 | 15 | @override 16 | void didChangeDependencies() { 17 | super.didChangeDependencies(); 18 | _skillController..addListener(_onTextChange); 19 | } 20 | 21 | void _onTextChange() { 22 | final _bloc = ResumeMakerBlocProvider.of(context); 23 | _bloc.skillIconSink.add(true); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Container( 29 | margin: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.18), 30 | child: buildForm(), 31 | ); 32 | } 33 | 34 | Widget buildForm() { 35 | final _bloc = ResumeMakerBlocProvider.of(context); 36 | return SingleChildScrollView( 37 | child: Form( 38 | key: _formKey, 39 | child: Padding( 40 | padding: const EdgeInsets.all(8.0), 41 | child: Column( 42 | mainAxisAlignment: MainAxisAlignment.start, 43 | crossAxisAlignment: CrossAxisAlignment.start, 44 | children: [ 45 | Row( 46 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 47 | children: [ 48 | OnBoardingHeaderText(text: 'Skills'), 49 | Padding( 50 | padding: const EdgeInsets.all(16.0), 51 | child: ClipOval( 52 | child: Container( 53 | height: 32, 54 | width: 32, 55 | color: Theme.of(context).primaryColor, 56 | child: IconButton( 57 | padding: EdgeInsets.all(4), 58 | color: Colors.white, 59 | onPressed: () => _addSkills(), 60 | icon: StreamBuilder( 61 | stream: _bloc.skillIconStream, 62 | builder: (context, snapshot) { 63 | IconData iconDate = snapshot.hasData ? snapshot.data ? Icons.done: Icons.add: Icons.add; 64 | return Icon(iconDate); 65 | } 66 | ), 67 | )), 68 | ), 69 | ), 70 | ], 71 | ), 72 | Padding( 73 | padding: const EdgeInsets.all(16.0), 74 | child: CustomTextFieldForm( 75 | controller: _skillController, 76 | hintText: 'Add Skill ...', 77 | helperText: 'Your skill', 78 | validator: (val) => val.length == 0 ? 'Empty skill' : null, 79 | ), 80 | ), 81 | _buildSkillsList(), 82 | ], 83 | ), 84 | ), 85 | ), 86 | ); 87 | } 88 | 89 | Widget _buildSkillsList() { 90 | final _bloc = ResumeMakerBlocProvider.of(context); 91 | return Padding( 92 | padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), 93 | child: StreamBuilder( 94 | stream: _bloc.skillListModifiedStream, 95 | builder: (context, snapshot) { 96 | return _bloc.educationList.isEmpty 97 | ? Container( 98 | child: Text('No skill added'), 99 | ) 100 | : Wrap( 101 | children: _buildSkillList(_bloc.skillList), 102 | ); 103 | }), 104 | ); 105 | } 106 | 107 | _buildSkillList(List skillList) { 108 | List skills = List(); 109 | skillList.forEach((item) { 110 | skills.add(Container( 111 | margin: EdgeInsets.only(right: 12.0, bottom: 4), 112 | child: Chip( 113 | deleteIcon: Icon( 114 | Icons.close, 115 | size: 16, 116 | color: Colors.white, 117 | ), 118 | label: Text( 119 | item, 120 | style: TextStyle( 121 | fontSize: MediaQuery.of(context).size.shortestSide * 0.04, 122 | letterSpacing: 0.8, 123 | color: Colors.white, 124 | ), 125 | ), 126 | onDeleted: () => _deleteSkills(item), 127 | elevation: 4, 128 | backgroundColor: Theme.of(context).primaryColor, 129 | ), 130 | )); 131 | }); 132 | return skills; 133 | } 134 | 135 | void _addSkills() async { 136 | FocusScope.of(context).requestFocus(new FocusNode()); 137 | if (_formKey.currentState.validate()) { 138 | _formKey.currentState.save(); 139 | final _bloc = ResumeMakerBlocProvider.of(context); 140 | _bloc.skillList.add(_skillController.text); 141 | _bloc.skillListModifiedSink.add(_skillController.text); 142 | _bloc.skillList.isNotEmpty ? _bloc.nextButtonEnableSink.add(true) : _bloc.nextButtonEnableSink.add(false); 143 | _skillController.text = ''; 144 | _bloc.skillIconSink.add(false); 145 | } 146 | } 147 | 148 | void _deleteSkills(String skill) async { 149 | FocusScope.of(context).requestFocus(new FocusNode()); 150 | final _bloc = ResumeMakerBlocProvider.of(context); 151 | _bloc.skillList.remove(skill); 152 | _bloc.skillListModifiedSink.add(skill); 153 | _bloc.skillList.isNotEmpty ? _bloc.nextButtonEnableSink.add(true) : _bloc.nextButtonEnableSink.add(false); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /lib/screen/resume_maker/tabs/profile_tab.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:resumepad/model/resume_model.dart'; 3 | import 'package:resumepad/screen/resume_maker/bloc/bloc_provider.dart'; 4 | import 'package:resumepad/widget/custom_text_feild_form.dart'; 5 | import 'package:resumepad/widget/rounded_button.dart'; 6 | 7 | class ProfileTab extends StatefulWidget { 8 | @override 9 | _ProfileTabState createState() => _ProfileTabState(); 10 | } 11 | 12 | class _ProfileTabState extends State { 13 | TextEditingController _nameController; 14 | TextEditingController _designationController; 15 | TextEditingController _cityController; 16 | final _formKey = GlobalKey(); 17 | 18 | @override 19 | void initState() { 20 | super.initState(); 21 | _nameController = TextEditingController(); 22 | _designationController = TextEditingController(); 23 | _cityController = TextEditingController(); 24 | } 25 | 26 | @override 27 | void didChangeDependencies() { 28 | super.didChangeDependencies(); 29 | final _bloc = ResumeMakerBlocProvider.of(context); 30 | _nameController.text = _bloc.profile != null ? _bloc.profile.name : ''; 31 | _designationController.text = _bloc.profile != null ? _bloc.profile.designation : ''; 32 | _cityController.text = _bloc.profile != null ? _bloc.profile.currentCityAndCountry : ''; 33 | _nameController..addListener(_onNameChange); 34 | _designationController..addListener(_onDesignationChange); 35 | _cityController..addListener(_onCityChange); 36 | } 37 | 38 | void _onNameChange() { 39 | final _bloc = ResumeMakerBlocProvider.of(context); 40 | if (_bloc.profile != null && _nameController.text != _bloc.profile.name) { 41 | _bloc.saveProfileButtonEnableSink.add(true); 42 | _bloc.nextButtonEnableSink.add(false); 43 | } 44 | } 45 | 46 | void _onDesignationChange() { 47 | final _bloc = ResumeMakerBlocProvider.of(context); 48 | if (_bloc.profile != null && _designationController.text != _bloc.profile.designation) { 49 | _bloc.saveProfileButtonEnableSink.add(true); 50 | _bloc.nextButtonEnableSink.add(false); 51 | } 52 | } 53 | 54 | void _onCityChange() { 55 | final _bloc = ResumeMakerBlocProvider.of(context); 56 | if (_bloc.profile != null && _cityController.text != _bloc.profile.currentCityAndCountry) { 57 | _bloc.saveProfileButtonEnableSink.add(true); 58 | _bloc.nextButtonEnableSink.add(false); 59 | } 60 | } 61 | 62 | @override 63 | void dispose() { 64 | super.dispose(); 65 | _nameController.dispose(); 66 | _designationController.dispose(); 67 | _cityController.dispose(); 68 | } 69 | 70 | @override 71 | Widget build(BuildContext context) { 72 | return Container( 73 | margin: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.3), 74 | child: _buildForm(), 75 | ); 76 | } 77 | 78 | Widget _buildForm() { 79 | final _bloc = ResumeMakerBlocProvider.of(context); 80 | return SingleChildScrollView( 81 | child: Form( 82 | key: _formKey, 83 | child: Padding( 84 | padding: const EdgeInsets.all(8.0), 85 | child: Column( 86 | mainAxisAlignment: MainAxisAlignment.start, 87 | children: [ 88 | Padding( 89 | padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), 90 | child: CustomTextFieldForm( 91 | controller: _nameController, 92 | hintText: 'What is your name?', 93 | helperText: 'Your name', 94 | validator: (val) => val.length == 0 ? 'Empty name' : val.length < 2 ? 'Invalid name' : null, 95 | ), 96 | ), 97 | Padding( 98 | padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), 99 | child: CustomTextFieldForm( 100 | controller: _designationController, 101 | hintText: 'What is your designation?', 102 | helperText: 'Your designation', 103 | validator: (val) => 104 | val.length == 0 ? 'Empty designation' : val.length < 2 ? 'Invalid designation' : null, 105 | ), 106 | ), 107 | Padding( 108 | padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), 109 | child: CustomTextFieldForm( 110 | controller: _cityController, 111 | hintText: 'City, Country (eg: Madrid, Spain)', 112 | helperText: 'Your residing city', 113 | validator: (val) => val.length == 0 ? 'Invalid' : null, 114 | ), 115 | ), 116 | SizedBox( 117 | height: 24, 118 | ), 119 | StreamBuilder( 120 | stream: _bloc.saveProfileButtonEnableStream, 121 | builder: (context, snapshot) { 122 | bool isEnable = snapshot.hasData ? snapshot.data : false; 123 | return RoundedButton( 124 | text: 'Save', 125 | onPressed: isEnable ? _onAddProfile : null, 126 | ); 127 | }), 128 | ], 129 | ), 130 | ), 131 | ), 132 | ); 133 | } 134 | 135 | void _onAddProfile() { 136 | FocusScope.of(context).requestFocus(new FocusNode()); 137 | final _bloc = ResumeMakerBlocProvider.of(context); 138 | if (_bloc.userImage == null) { 139 | return; 140 | } 141 | if (_formKey.currentState.validate()) { 142 | _formKey.currentState.save(); 143 | _bloc.profile = Profile( 144 | name: _nameController.text, 145 | designation: _designationController.text, 146 | imagePath: _bloc.userImage.path, 147 | currentCityAndCountry: _cityController.text, 148 | ); 149 | _bloc.saveProfileButtonEnableSink.add(false); 150 | _bloc.nextButtonEnableSink.add(true); 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /lib/screen/resume_maker/widget/user_picture_widget.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:image_cropper/image_cropper.dart'; 5 | import 'package:image_picker/image_picker.dart'; 6 | import 'package:resumepad/screen/resume_maker/bloc/bloc_provider.dart'; 7 | import 'package:resumepad/screen/resume_maker/bloc/resume_maker_bloc.dart'; 8 | import 'package:resumepad/utility/constants.dart'; 9 | 10 | const widthFactor = 0.7; 11 | const heightFactor = 0.15; 12 | const scaleFactor = 0.5; 13 | 14 | class UserPictureWidget extends StatelessWidget { 15 | final AnimationController controller; 16 | 17 | UserPictureWidget(this.controller); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return Positioned( 22 | top: MediaQuery.of(context).size.width * 0.0, 23 | left: 0, 24 | right: 0, 25 | child: AnimatedBuilder( 26 | animation: CurvedAnimation(parent: controller, curve: Curves.fastOutSlowIn), 27 | builder: (context, child) { 28 | return Container( 29 | alignment: Alignment.center, 30 | height: MediaQuery.of(context).size.height * 0.4, 31 | child: getImageContainer(context), 32 | ); 33 | }, 34 | ), 35 | ); 36 | } 37 | 38 | Widget _buildContainer(double radius) { 39 | return Container( 40 | width: radius, 41 | height: radius, 42 | decoration: BoxDecoration( 43 | shape: BoxShape.circle, 44 | color: Colors.black87.withOpacity(1 - controller.value), 45 | ), 46 | ); 47 | } 48 | 49 | getImageContainer(BuildContext context) { 50 | final _bloc = ResumeMakerBlocProvider.of(context); 51 | return StreamBuilder( 52 | stream: _bloc.mainPagerStream, 53 | builder: (BuildContext buildContext, AsyncSnapshot snapshot) { 54 | double pageOffset = snapshot.hasData ? snapshot.data : 0.0; 55 | return Transform.scale( 56 | scale: _getScaleOffset(pageOffset), 57 | child: Transform.translate( 58 | offset: Offset(-_getXOffset(pageOffset, MediaQuery.of(context).size.width), 59 | -_getYOffset(pageOffset, MediaQuery.of(context).size.height)), 60 | child: Stack( 61 | alignment: Alignment.center, 62 | children: [ 63 | _buildContainer(MediaQuery.of(context).size.shortestSide * 0.46 * controller.value), 64 | _buildContainer(MediaQuery.of(context).size.shortestSide * 0.52 * controller.value), 65 | _buildContainer(MediaQuery.of(context).size.shortestSide * 0.60 * controller.value), 66 | Container( 67 | height: MediaQuery.of(context).size.shortestSide * 0.4, 68 | width: MediaQuery.of(context).size.shortestSide * 0.4, 69 | child: InkWell( 70 | onTap: _bloc.currentPage == NAVIGATE_TO_PROFILE_TAB ? () => getImage(_bloc) : null, 71 | child: Card( 72 | elevation: (1 + pageOffset) * 4, 73 | clipBehavior: Clip.antiAlias, 74 | shape: CircleBorder(side: BorderSide(color: Colors.grey.shade200, width: 2)), 75 | child: StreamBuilder( 76 | stream: _bloc.pictureClickStream, 77 | builder: (context, snapshot) { 78 | var image = snapshot.hasData ? snapshot.data : _bloc.userImage; 79 | return image != null 80 | ? Image.file(image) 81 | : Icon( 82 | Icons.add_a_photo, 83 | size: MediaQuery.of(context).size.shortestSide * 0.2, 84 | ); 85 | }), 86 | ), 87 | ), 88 | ), 89 | ], 90 | ), 91 | ), 92 | ); 93 | }); 94 | } 95 | 96 | Future getImage(ResumeMakerBloc _bloc) async { 97 | var image = await ImagePicker.pickImage(source: ImageSource.gallery); 98 | File croppedFile = await ImageCropper.cropImage( 99 | sourcePath: image.path, 100 | toolbarTitle: 'Crop', 101 | toolbarColor: Colors.blue, 102 | toolbarWidgetColor: Colors.white, 103 | ratioX: 1.0, 104 | ratioY: 1.0, 105 | maxWidth: 512, 106 | maxHeight: 512, 107 | ); 108 | _bloc.userImage = croppedFile; 109 | _bloc.pictureClickSink.add(croppedFile); 110 | _bloc.saveProfileButtonEnableSink.add(true); 111 | _bloc.nextButtonEnableSink.add(false); 112 | } 113 | 114 | double _getXOffset(double pageOffset, double width) { 115 | double xOffset = 0; 116 | if (pageOffset >= 1 && pageOffset <= 2) { 117 | xOffset = width * widthFactor * (pageOffset - 1); 118 | } else if (pageOffset > 8 && pageOffset <= 9) { 119 | xOffset = -width * widthFactor * (pageOffset - 9); 120 | } else if (pageOffset < 1) { 121 | xOffset = 0; 122 | } else { 123 | xOffset = width * widthFactor; 124 | } 125 | return xOffset; 126 | } 127 | 128 | double _getYOffset(double pageOffset, double height) { 129 | double yOffset = 0; 130 | if (pageOffset <= 1) { 131 | yOffset = height * heightFactor * pageOffset; 132 | } else if (pageOffset > 8 && pageOffset <= 9) { 133 | yOffset = height * heightFactor * (9 - pageOffset); 134 | } else { 135 | yOffset = height * heightFactor; 136 | } 137 | return yOffset; 138 | } 139 | 140 | double _getScaleOffset(double pageOffset) { 141 | double scale = scaleFactor; 142 | if (pageOffset >= 0 && pageOffset <= 1) { 143 | scale = 1 - pageOffset / 2; 144 | } else if (pageOffset > 8 && pageOffset <= 9) { 145 | scale = scaleFactor + (pageOffset - 8) / 2; 146 | } else { 147 | scale = scaleFactor; 148 | } 149 | return scale; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /lib/screen/add_education/add_education_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:resumepad/model/resume_model.dart'; 3 | import 'package:resumepad/widget/custom_text_feild_form.dart'; 4 | import 'package:resumepad/widget/date_picker_field.dart'; 5 | import 'package:resumepad/widget/rounded_button.dart'; 6 | 7 | import 'bloc/add_education_bloc.dart'; 8 | import 'bloc/bloc_provider.dart'; 9 | 10 | class AddEducationDialog extends StatefulWidget { 11 | final Education education; 12 | 13 | AddEducationDialog({this.education}); 14 | 15 | @override 16 | _AddEducationDialogState createState() => _AddEducationDialogState(); 17 | } 18 | 19 | class _AddEducationDialogState extends State { 20 | TextEditingController _universityController; 21 | TextEditingController _courseController; 22 | TextEditingController _linkController; 23 | final _formKey = GlobalKey(); 24 | AddEducationBloc _bloc; 25 | 26 | @override 27 | void initState() { 28 | super.initState(); 29 | _bloc = AddEducationBloc(); 30 | _universityController = TextEditingController(text: widget.education != null ? widget.education.universityName: ''); 31 | _courseController = TextEditingController(text: widget.education != null ? widget.education.courseTaken: ''); 32 | _linkController = TextEditingController(text: widget.education != null ? widget.education.collegeLink: ''); 33 | 34 | _bloc.startDate = widget.education != null ? widget.education.startDate: DateTime.now(); 35 | _bloc.endDate = widget.education != null ? widget.education.endDate: DateTime.now(); 36 | } 37 | 38 | @override 39 | void dispose() { 40 | _bloc.dispose(); 41 | super.dispose(); 42 | } 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | return AddEducationBlocProvider( 47 | bloc: _bloc, 48 | child: Scaffold( 49 | body: _buildForm(), 50 | appBar: AppBar( 51 | backgroundColor: Colors.transparent, 52 | elevation: 0, 53 | iconTheme: IconThemeData(color: Colors.black), 54 | title: Text( 55 | 'Add College', 56 | style: TextStyle( 57 | color: Colors.black, 58 | ), 59 | ), 60 | ), 61 | ), 62 | ); 63 | } 64 | 65 | Widget _buildForm() { 66 | return SingleChildScrollView( 67 | child: Form( 68 | key: _formKey, 69 | child: Padding( 70 | padding: const EdgeInsets.all(24.0), 71 | child: Column( 72 | mainAxisSize: MainAxisSize.min, 73 | mainAxisAlignment: MainAxisAlignment.start, 74 | crossAxisAlignment: CrossAxisAlignment.start, 75 | children: [ 76 | CustomTextFieldForm( 77 | controller: _universityController, 78 | hintText: 'Name of university ...', 79 | helperText: 'Your university', 80 | validator: (val) => val.length == 0 ? 'Empty name' : val.length < 2 ? 'Invalid name' : null, 81 | ), 82 | SizedBox(height: 12.0), 83 | CustomTextFieldForm( 84 | controller: _linkController, 85 | hintText: 'University website', 86 | helperText: 'University link', 87 | validator: null, 88 | ), 89 | SizedBox(height: 12.0), 90 | CustomTextFieldForm( 91 | controller: _courseController, 92 | hintText: 'What course you took?', 93 | helperText: 'Your course', 94 | validator: (val) => val.length == 0 ? 'Empty course' : null, 95 | ), 96 | SizedBox(height: 12.0), 97 | _DateRow(), 98 | SizedBox(height: 24.0), 99 | RoundedButton( 100 | text: 'Add', 101 | onPressed: onAddEducation, 102 | ), 103 | ], 104 | ), 105 | ), 106 | ), 107 | ); 108 | } 109 | 110 | void onAddEducation() { 111 | FocusScope.of(context).requestFocus(new FocusNode()); 112 | if (_formKey.currentState.validate()) { 113 | _formKey.currentState.save(); 114 | 115 | if (_bloc.startDate.isAfter(_bloc.endDate)) { 116 | _bloc.errorSink.add('Invalid date'); 117 | return; 118 | } 119 | 120 | Navigator.of(context).pop( 121 | Education( 122 | collegeLink: _linkController.text, 123 | startDate: _bloc.startDate, 124 | endDate: _bloc.endDate, 125 | courseTaken: _courseController.text, 126 | universityName: _universityController.text, 127 | ), 128 | ); 129 | } 130 | } 131 | } 132 | 133 | class _DateRow extends StatelessWidget { 134 | @override 135 | Widget build(BuildContext context) { 136 | final _bloc = AddEducationBlocProvider.of(context); 137 | return StreamBuilder( 138 | stream: _bloc.errorStream, 139 | builder: (context, errorSnapshot) { 140 | String error = errorSnapshot.hasData ? errorSnapshot.data : null; 141 | return Row( 142 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 143 | crossAxisAlignment: CrossAxisAlignment.end, 144 | mainAxisSize: MainAxisSize.max, 145 | children: [ 146 | Expanded( 147 | child: StreamBuilder( 148 | stream: _bloc.startDateStream, 149 | builder: (context, snapshot) { 150 | _bloc.startDate = snapshot.hasData ? snapshot.data : _bloc.startDate; 151 | return DatePicker( 152 | labelText: 'Start', 153 | errorText: error, 154 | dateTime: _bloc.startDate, 155 | onChanged: (dateTime) => _bloc.startDateSink.add(dateTime), 156 | ); 157 | }), 158 | ), 159 | SizedBox( 160 | width: 24, 161 | ), 162 | Expanded( 163 | child: StreamBuilder( 164 | stream: _bloc.endDateStream, 165 | builder: (context, snapshot) { 166 | _bloc.endDate = snapshot.hasData ? snapshot.data : _bloc.endDate; 167 | return DatePicker( 168 | labelText: 'End', 169 | errorText: error, 170 | dateTime: _bloc.endDate, 171 | onChanged: (dateTime) => _bloc.endDateSink.add(dateTime), 172 | ); 173 | }), 174 | ), 175 | ], 176 | ); 177 | }); 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /lib/screen/add_project/add_project_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:resumepad/model/resume_model.dart'; 3 | import 'package:resumepad/screen/add_project/bloc/bloc_provider.dart'; 4 | import 'package:resumepad/widget/custom_text_feild_form.dart'; 5 | import 'package:resumepad/widget/date_picker_field.dart'; 6 | import 'package:resumepad/widget/rounded_button.dart'; 7 | 8 | import 'bloc/add_project_bloc.dart'; 9 | 10 | class AddProjectDialog extends StatefulWidget { 11 | final Project project; 12 | 13 | AddProjectDialog({this.project}); 14 | 15 | @override 16 | _AddProjectDialogState createState() => _AddProjectDialogState(); 17 | } 18 | 19 | class _AddProjectDialogState extends State { 20 | TextEditingController _nameController; 21 | TextEditingController _linkController; 22 | TextEditingController _summaryController; 23 | 24 | final _formKey = GlobalKey(); 25 | AddProjectBloc _bloc; 26 | 27 | @override 28 | void initState() { 29 | super.initState(); 30 | _bloc = AddProjectBloc(); 31 | 32 | _nameController = TextEditingController(text: widget.project != null ? widget.project.projectName: ''); 33 | _summaryController = TextEditingController(text: widget.project != null ? widget.project.projectSummary: ''); 34 | _linkController = TextEditingController(text: widget.project != null ? widget.project.projectLink: ''); 35 | 36 | _bloc.startDate = widget.project != null ? widget.project.startDate: DateTime.now(); 37 | _bloc.endDate = widget.project != null ? widget.project.endDate: DateTime.now(); 38 | } 39 | 40 | @override 41 | void dispose() { 42 | _bloc.dispose(); 43 | _nameController.dispose(); 44 | _summaryController.dispose(); 45 | _linkController.dispose(); 46 | super.dispose(); 47 | } 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return AddProjectBlocProvider( 52 | bloc: _bloc, 53 | child: Scaffold( 54 | body: _buildForm(), 55 | appBar: AppBar( 56 | backgroundColor: Colors.transparent, 57 | elevation: 0, 58 | iconTheme: IconThemeData(color: Colors.black), 59 | title: Text( 60 | 'Add Project', 61 | style: TextStyle( 62 | color: Colors.black, 63 | ), 64 | ), 65 | ), 66 | ), 67 | ); 68 | } 69 | 70 | Widget _buildForm() { 71 | return SingleChildScrollView( 72 | child: Form( 73 | key: _formKey, 74 | child: Padding( 75 | padding: const EdgeInsets.all(24.0), 76 | child: Column( 77 | mainAxisSize: MainAxisSize.min, 78 | mainAxisAlignment: MainAxisAlignment.start, 79 | crossAxisAlignment: CrossAxisAlignment.start, 80 | children: [ 81 | CustomTextFieldForm( 82 | controller: _nameController, 83 | hintText: 'Name of project ...', 84 | helperText: 'Your project', 85 | validator: (val) => val.length == 0 ? 'Empty name' : val.length < 2 ? 'Invalid name' : null, 86 | ), 87 | SizedBox(height: 12.0), 88 | CustomTextFieldForm( 89 | controller: _linkController, 90 | hintText: 'Project website', 91 | helperText: 'Project link', 92 | validator: null, 93 | ), 94 | SizedBox(height: 12.0), 95 | CustomTextFieldForm( 96 | controller: _summaryController, 97 | hintText: 'What was your contribution?', 98 | helperText: 'Project summary', 99 | validator: null, 100 | maxLines: 10, 101 | inputBorder: OutlineInputBorder(), 102 | ), 103 | SizedBox(height: 12.0), 104 | _DateRow(), 105 | SizedBox(height: 24.0), 106 | RoundedButton( 107 | text: 'Add', 108 | onPressed: onAddProject, 109 | ), 110 | ], 111 | ), 112 | ), 113 | ), 114 | ); 115 | } 116 | 117 | void onAddProject() { 118 | FocusScope.of(context).requestFocus(new FocusNode()); 119 | if (_formKey.currentState.validate()) { 120 | _formKey.currentState.save(); 121 | 122 | if (_bloc.startDate.isAfter(_bloc.endDate)) { 123 | _bloc.errorSink.add('Invalid date'); 124 | return; 125 | } 126 | 127 | Navigator.of(context).pop( 128 | Project( 129 | projectLink: _linkController.value.text, 130 | startDate: _bloc.startDate, 131 | endDate: _bloc.endDate, 132 | projectName: _nameController.value.text, 133 | projectSummary: _summaryController.value.text, 134 | ), 135 | ); 136 | } 137 | } 138 | } 139 | 140 | class _DateRow extends StatelessWidget { 141 | @override 142 | Widget build(BuildContext context) { 143 | final _bloc = AddProjectBlocProvider.of(context); 144 | return StreamBuilder( 145 | stream: _bloc.errorStream, 146 | builder: (context, errorSnapshot) { 147 | String error = errorSnapshot.hasData ? errorSnapshot.data : null; 148 | return Row( 149 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 150 | crossAxisAlignment: CrossAxisAlignment.end, 151 | mainAxisSize: MainAxisSize.max, 152 | children: [ 153 | Expanded( 154 | child: StreamBuilder( 155 | stream: _bloc.startDateStream, 156 | builder: (context, snapshot) { 157 | _bloc.startDate = snapshot.hasData ? snapshot.data : _bloc.startDate; 158 | return DatePicker( 159 | labelText: 'Start', 160 | errorText: error, 161 | dateTime: _bloc.startDate, 162 | onChanged: (dateTime) => _bloc.startDateSink.add(dateTime), 163 | ); 164 | }), 165 | ), 166 | SizedBox( 167 | width: 24, 168 | ), 169 | Expanded( 170 | child: StreamBuilder( 171 | stream: _bloc.endDateStream, 172 | builder: (context, snapshot) { 173 | _bloc.endDate = snapshot.hasData ? snapshot.data : _bloc.endDate; 174 | return DatePicker( 175 | labelText: 'End', 176 | errorText: error, 177 | dateTime: _bloc.endDate, 178 | onChanged: (dateTime) => _bloc.endDateSink.add(dateTime), 179 | ); 180 | }), 181 | ), 182 | ], 183 | ); 184 | }); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /lib/screen/resume_maker/tabs/language_tab.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:resumepad/model/resume_model.dart'; 3 | import 'package:resumepad/screen/add_language/add_language_dialog.dart'; 4 | import 'package:resumepad/screen/resume_maker/bloc/bloc_provider.dart'; 5 | import 'package:resumepad/screen/resume_maker/widget/onboarding_header_text.dart'; 6 | import 'package:resumepad/utility/color_utility.dart'; 7 | 8 | class LanguageTab extends StatefulWidget { 9 | @override 10 | _LanguageTabState createState() => _LanguageTabState(); 11 | } 12 | 13 | class _LanguageTabState extends State { 14 | final TextEditingController summaryController = TextEditingController(); 15 | 16 | final formKey = GlobalKey(); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Container( 21 | margin: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.18), 22 | child: _buildList(), 23 | ); 24 | } 25 | 26 | Widget _buildList() { 27 | return Padding( 28 | padding: const EdgeInsets.all(8.0), 29 | child: Column( 30 | mainAxisAlignment: MainAxisAlignment.start, 31 | crossAxisAlignment: CrossAxisAlignment.start, 32 | children: [ 33 | Row( 34 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 35 | children: [ 36 | OnBoardingHeaderText(text: 'Language'), 37 | Padding( 38 | padding: const EdgeInsets.all(16.0), 39 | child: ClipOval( 40 | child: Container( 41 | height: 32, 42 | width: 32, 43 | color: Theme.of(context).primaryColor, 44 | child: IconButton( 45 | padding: EdgeInsets.all(4), 46 | color: Colors.white, 47 | onPressed: () => _showAddLanguageDialog(null, 0), 48 | icon: const Icon(Icons.add), 49 | )), 50 | ), 51 | ), 52 | ], 53 | ), 54 | Expanded(child: _buildLanguageList()), 55 | ], 56 | ), 57 | ); 58 | } 59 | 60 | Widget _buildLanguageList() { 61 | final _bloc = ResumeMakerBlocProvider.of(context); 62 | return Padding( 63 | padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), 64 | child: StreamBuilder( 65 | stream: _bloc.languageListModifiedStream, 66 | builder: (context, snapshot) { 67 | return _bloc.languageList.isEmpty 68 | ? Container( 69 | child: Text('No language added'), 70 | ) 71 | : ListView.builder( 72 | itemBuilder: (BuildContext context, int index) { 73 | return _bloc.languageList[index] != null 74 | ? _LanguageCard( 75 | language: _bloc.languageList[index], 76 | onTap: () => _showAddLanguageDialog( 77 | _bloc.languageList[index], 78 | index, 79 | ), 80 | ) 81 | : Offstage(); 82 | }, 83 | itemCount: _bloc.languageList.length, 84 | ); 85 | }), 86 | ); 87 | } 88 | 89 | Future _showAddLanguageDialog(Language tempLanguage, int index) async { 90 | Language language = await Navigator.of(context).push( 91 | MaterialPageRoute( 92 | builder: (BuildContext context) { 93 | return AddLanguageDialog( 94 | language: tempLanguage, 95 | ); 96 | }, 97 | fullscreenDialog: true), 98 | ); 99 | 100 | if(language != null) { 101 | final _bloc = ResumeMakerBlocProvider.of(context); 102 | if (tempLanguage != null) { 103 | _bloc.languageList[index] = language; 104 | } else { 105 | _bloc.languageList.add(language); 106 | } 107 | 108 | _bloc.languageList.isNotEmpty ? _bloc.nextButtonEnableSink.add(true) : _bloc.nextButtonEnableSink.add(false); 109 | _bloc.languageListModifiedSink.add(language); 110 | } 111 | } 112 | } 113 | 114 | class _LanguageCard extends StatelessWidget { 115 | final Language language; 116 | final Function onTap; 117 | 118 | _LanguageCard({ 119 | @required this.language, 120 | @required this.onTap, 121 | }); 122 | 123 | @override 124 | Widget build(BuildContext context) { 125 | return Dismissible( 126 | key: Key(language.toString()), 127 | background: new Container( 128 | padding: EdgeInsets.only(right: 20.0), 129 | color: Colors.red, 130 | child: new Align( 131 | alignment: Alignment.centerRight, 132 | child: new Text('Delete', textAlign: TextAlign.right, style: new TextStyle(color: Colors.white)), 133 | )), 134 | direction: DismissDirection.endToStart, 135 | onDismissed: (direction) { 136 | final _bloc = ResumeMakerBlocProvider.of(context); 137 | _bloc.languageList.remove(language); 138 | _bloc.languageListModifiedSink.add(language); 139 | _bloc.languageList.isNotEmpty ? _bloc.nextButtonEnableSink.add(true) : _bloc.nextButtonEnableSink.add(false); 140 | }, 141 | child: _tapableContent(context), 142 | ); 143 | } 144 | 145 | _tapableContent(BuildContext context) => InkWell( 146 | onTap: onTap, 147 | child: Container( 148 | width: double.infinity, 149 | child: Card( 150 | child: Padding( 151 | padding: const EdgeInsets.all(16.0), 152 | child: Column( 153 | mainAxisAlignment: MainAxisAlignment.start, 154 | crossAxisAlignment: CrossAxisAlignment.start, 155 | children: [ 156 | Text( 157 | '${language != null ? language.name : ''}', 158 | style: TextStyle( 159 | color: Color(getColorHexFromStr(TEXT_COLOR_BLACK)), 160 | fontSize: MediaQuery.of(context).size.shortestSide * 0.05, 161 | letterSpacing: 0.8), 162 | softWrap: true, 163 | textAlign: TextAlign.left, 164 | ), 165 | SizedBox( 166 | height: 4, 167 | ), 168 | Text( 169 | language != null ? language.level : '', 170 | style: TextStyle( 171 | color: Color(getColorHexFromStr(TEXT_COLOR_BLACK)), 172 | fontSize: MediaQuery.of(context).size.shortestSide * 0.04, 173 | fontWeight: FontWeight.w500, 174 | letterSpacing: 0.8), 175 | softWrap: true, 176 | textAlign: TextAlign.left, 177 | ), 178 | ], 179 | ), 180 | ), 181 | ), 182 | ), 183 | ); 184 | } 185 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | archive: 5 | dependency: transitive 6 | description: 7 | name: archive 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.0.10" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.5.2" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.2.0" 25 | boolean_selector: 26 | dependency: transitive 27 | description: 28 | name: boolean_selector 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.0.4" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.2" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.14.11" 46 | convert: 47 | dependency: transitive 48 | description: 49 | name: convert 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.1.1" 53 | crypto: 54 | dependency: transitive 55 | description: 56 | name: crypto 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.1.2" 60 | cupertino_icons: 61 | dependency: "direct main" 62 | description: 63 | name: cupertino_icons 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "0.1.2" 67 | flutter: 68 | dependency: "direct main" 69 | description: flutter 70 | source: sdk 71 | version: "0.0.0" 72 | flutter_full_pdf_viewer: 73 | dependency: "direct main" 74 | description: 75 | name: flutter_full_pdf_viewer 76 | url: "https://pub.dartlang.org" 77 | source: hosted 78 | version: "1.0.4" 79 | flutter_launcher_icons: 80 | dependency: "direct dev" 81 | description: 82 | name: flutter_launcher_icons 83 | url: "https://pub.dartlang.org" 84 | source: hosted 85 | version: "0.7.2+1" 86 | flutter_test: 87 | dependency: "direct dev" 88 | description: flutter 89 | source: sdk 90 | version: "0.0.0" 91 | image: 92 | dependency: transitive 93 | description: 94 | name: image 95 | url: "https://pub.dartlang.org" 96 | source: hosted 97 | version: "2.1.4" 98 | image_cropper: 99 | dependency: "direct main" 100 | description: 101 | name: image_cropper 102 | url: "https://pub.dartlang.org" 103 | source: hosted 104 | version: "1.0.2" 105 | image_picker: 106 | dependency: "direct main" 107 | description: 108 | name: image_picker 109 | url: "https://pub.dartlang.org" 110 | source: hosted 111 | version: "0.6.1+4" 112 | intl: 113 | dependency: "direct main" 114 | description: 115 | name: intl 116 | url: "https://pub.dartlang.org" 117 | source: hosted 118 | version: "0.15.8" 119 | matcher: 120 | dependency: transitive 121 | description: 122 | name: matcher 123 | url: "https://pub.dartlang.org" 124 | source: hosted 125 | version: "0.12.5" 126 | meta: 127 | dependency: transitive 128 | description: 129 | name: meta 130 | url: "https://pub.dartlang.org" 131 | source: hosted 132 | version: "1.1.6" 133 | path: 134 | dependency: transitive 135 | description: 136 | name: path 137 | url: "https://pub.dartlang.org" 138 | source: hosted 139 | version: "1.6.2" 140 | path_provider: 141 | dependency: "direct main" 142 | description: 143 | name: path_provider 144 | url: "https://pub.dartlang.org" 145 | source: hosted 146 | version: "1.2.0" 147 | pdf: 148 | dependency: "direct main" 149 | description: 150 | name: pdf 151 | url: "https://pub.dartlang.org" 152 | source: hosted 153 | version: "1.3.18" 154 | pedantic: 155 | dependency: transitive 156 | description: 157 | name: pedantic 158 | url: "https://pub.dartlang.org" 159 | source: hosted 160 | version: "1.7.0" 161 | petitparser: 162 | dependency: transitive 163 | description: 164 | name: petitparser 165 | url: "https://pub.dartlang.org" 166 | source: hosted 167 | version: "2.4.0" 168 | quiver: 169 | dependency: transitive 170 | description: 171 | name: quiver 172 | url: "https://pub.dartlang.org" 173 | source: hosted 174 | version: "2.0.3" 175 | rxdart: 176 | dependency: "direct main" 177 | description: 178 | name: rxdart 179 | url: "https://pub.dartlang.org" 180 | source: hosted 181 | version: "0.22.1+1" 182 | sky_engine: 183 | dependency: transitive 184 | description: flutter 185 | source: sdk 186 | version: "0.0.99" 187 | source_span: 188 | dependency: transitive 189 | description: 190 | name: source_span 191 | url: "https://pub.dartlang.org" 192 | source: hosted 193 | version: "1.5.5" 194 | stack_trace: 195 | dependency: transitive 196 | description: 197 | name: stack_trace 198 | url: "https://pub.dartlang.org" 199 | source: hosted 200 | version: "1.9.3" 201 | stream_channel: 202 | dependency: transitive 203 | description: 204 | name: stream_channel 205 | url: "https://pub.dartlang.org" 206 | source: hosted 207 | version: "2.0.0" 208 | string_scanner: 209 | dependency: transitive 210 | description: 211 | name: string_scanner 212 | url: "https://pub.dartlang.org" 213 | source: hosted 214 | version: "1.0.4" 215 | term_glyph: 216 | dependency: transitive 217 | description: 218 | name: term_glyph 219 | url: "https://pub.dartlang.org" 220 | source: hosted 221 | version: "1.1.0" 222 | test_api: 223 | dependency: transitive 224 | description: 225 | name: test_api 226 | url: "https://pub.dartlang.org" 227 | source: hosted 228 | version: "0.2.5" 229 | typed_data: 230 | dependency: transitive 231 | description: 232 | name: typed_data 233 | url: "https://pub.dartlang.org" 234 | source: hosted 235 | version: "1.1.6" 236 | utf: 237 | dependency: transitive 238 | description: 239 | name: utf 240 | url: "https://pub.dartlang.org" 241 | source: hosted 242 | version: "0.9.0+5" 243 | vector_math: 244 | dependency: transitive 245 | description: 246 | name: vector_math 247 | url: "https://pub.dartlang.org" 248 | source: hosted 249 | version: "2.0.8" 250 | wc_flutter_share: 251 | dependency: "direct main" 252 | description: 253 | name: wc_flutter_share 254 | url: "https://pub.dartlang.org" 255 | source: hosted 256 | version: "0.1.1" 257 | xml: 258 | dependency: transitive 259 | description: 260 | name: xml 261 | url: "https://pub.dartlang.org" 262 | source: hosted 263 | version: "3.5.0" 264 | yaml: 265 | dependency: transitive 266 | description: 267 | name: yaml 268 | url: "https://pub.dartlang.org" 269 | source: hosted 270 | version: "2.1.16" 271 | sdks: 272 | dart: ">=2.4.0 <3.0.0" 273 | flutter: ">=1.5.0 <2.0.0" 274 | -------------------------------------------------------------------------------- /lib/screen/add_experience/add_experience_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:intl/intl.dart'; 3 | import 'package:resumepad/model/resume_model.dart'; 4 | import 'package:resumepad/screen/add_experience/bloc/bloc_provider.dart'; 5 | import 'package:resumepad/widget/custom_text_feild_form.dart'; 6 | import 'package:resumepad/widget/date_picker_field.dart'; 7 | import 'package:resumepad/widget/rounded_button.dart'; 8 | 9 | import 'bloc/add_experience_bloc.dart'; 10 | 11 | class AddExperienceDialog extends StatefulWidget { 12 | final Experience experience; 13 | 14 | AddExperienceDialog({this.experience}); 15 | 16 | @override 17 | _AddExperienceDialogState createState() => _AddExperienceDialogState(); 18 | } 19 | 20 | class _AddExperienceDialogState extends State { 21 | TextEditingController _nameController; 22 | 23 | TextEditingController _designationController; 24 | 25 | TextEditingController _linkController; 26 | 27 | TextEditingController _summaryController; 28 | 29 | final _formKey = GlobalKey(); 30 | AddExperienceBloc _bloc; 31 | 32 | @override 33 | void initState() { 34 | super.initState(); 35 | _bloc = AddExperienceBloc(); 36 | 37 | _nameController = TextEditingController(text: widget.experience != null ? widget.experience.companyName : ''); 38 | _designationController = 39 | TextEditingController(text: widget.experience != null ? widget.experience.designation : ''); 40 | _linkController = TextEditingController(text: widget.experience != null ? widget.experience.companyLink : ''); 41 | _summaryController = TextEditingController(text: widget.experience != null ? widget.experience.summary : ''); 42 | _bloc.startDate = widget.experience != null ? widget.experience.startDate : DateTime.now(); 43 | _bloc.endDate = widget.experience != null ? widget.experience.endDate : DateTime.now(); 44 | } 45 | 46 | @override 47 | void dispose() { 48 | _bloc.dispose(); 49 | super.dispose(); 50 | } 51 | 52 | @override 53 | Widget build(BuildContext context) { 54 | return AddExperienceBlocProvider( 55 | bloc: _bloc, 56 | child: Scaffold( 57 | body: _buildForm(), 58 | appBar: AppBar( 59 | backgroundColor: Colors.transparent, 60 | elevation: 0, 61 | iconTheme: IconThemeData(color: Colors.black), 62 | title: Text( 63 | 'Add Experience', 64 | style: TextStyle( 65 | color: Colors.black, 66 | ), 67 | ), 68 | ), 69 | ), 70 | ); 71 | } 72 | 73 | Widget _buildForm() { 74 | return SingleChildScrollView( 75 | child: Form( 76 | key: _formKey, 77 | child: Padding( 78 | padding: const EdgeInsets.all(24.0), 79 | child: Column( 80 | mainAxisSize: MainAxisSize.min, 81 | mainAxisAlignment: MainAxisAlignment.start, 82 | crossAxisAlignment: CrossAxisAlignment.start, 83 | children: [ 84 | CustomTextFieldForm( 85 | controller: _nameController, 86 | hintText: 'Name of company ...', 87 | helperText: 'Your company', 88 | validator: (val) => val.length == 0 ? 'Empty name' : val.length < 2 ? 'Invalid name' : null, 89 | ), 90 | SizedBox(height: 16.0), 91 | CustomTextFieldForm( 92 | controller: _linkController, 93 | hintText: 'Company website', 94 | helperText: 'Company link', 95 | validator: null, 96 | ), 97 | SizedBox(height: 16.0), 98 | CustomTextFieldForm( 99 | controller: _designationController, 100 | hintText: 'What was your role?', 101 | helperText: 'Your designation', 102 | validator: (val) => 103 | val.length == 0 ? 'Empty designation' : val.length < 2 ? 'Invalid designation' : null, 104 | ), 105 | SizedBox(height: 16.0), 106 | CustomTextFieldForm( 107 | controller: _summaryController, 108 | hintText: 'What was your contribution?', 109 | helperText: 'Job summary', 110 | validator: null, 111 | maxLines: 10, 112 | inputBorder: OutlineInputBorder(), 113 | ), 114 | SizedBox(height: 16.0), 115 | _DateRow(), 116 | SizedBox(height: 24.0), 117 | RoundedButton( 118 | text: 'Add', 119 | onPressed: onAddExperience, 120 | ), 121 | ], 122 | ), 123 | ), 124 | ), 125 | ); 126 | } 127 | 128 | void onAddExperience() { 129 | FocusScope.of(context).requestFocus(new FocusNode()); 130 | if (_formKey.currentState.validate()) { 131 | _formKey.currentState.save(); 132 | 133 | if (_bloc.startDate.isAfter(_bloc.endDate)) { 134 | _bloc.errorSink.add('Invalid date'); 135 | return; 136 | } 137 | 138 | Navigator.of(context).pop( 139 | Experience( 140 | companyLink: _linkController.value.text, 141 | startDate: _bloc.startDate, 142 | endDate: _bloc.endDate, 143 | designation: _designationController.value.text, 144 | companyName: _nameController.value.text, 145 | summary: _summaryController.value.text, 146 | ), 147 | ); 148 | } 149 | } 150 | } 151 | 152 | class _DateRow extends StatelessWidget { 153 | @override 154 | Widget build(BuildContext context) { 155 | final _bloc = AddExperienceBlocProvider.of(context); 156 | return StreamBuilder( 157 | stream: _bloc.errorStream, 158 | builder: (context, errorSnapshot) { 159 | String error = errorSnapshot.hasData ? errorSnapshot.data : null; 160 | return Row( 161 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 162 | crossAxisAlignment: CrossAxisAlignment.end, 163 | mainAxisSize: MainAxisSize.max, 164 | children: [ 165 | Expanded( 166 | child: StreamBuilder( 167 | stream: _bloc.startDateStream, 168 | builder: (context, snapshot) { 169 | _bloc.startDate = snapshot.hasData ? snapshot.data : _bloc.startDate; 170 | return DatePicker( 171 | labelText: 'Start', 172 | errorText: error, 173 | dateTime: _bloc.startDate, 174 | onChanged: (dateTime) => _bloc.startDateSink.add(dateTime), 175 | ); 176 | }), 177 | ), 178 | SizedBox( 179 | width: 24, 180 | ), 181 | Expanded( 182 | child: StreamBuilder( 183 | stream: _bloc.endDateStream, 184 | builder: (context, snapshot) { 185 | _bloc.endDate = snapshot.hasData ? snapshot.data : _bloc.endDate; 186 | return DatePicker( 187 | labelText: 'End', 188 | errorText: error, 189 | dateTime: _bloc.endDate, 190 | onChanged: (dateTime) => _bloc.endDateSink.add(dateTime), 191 | ); 192 | }), 193 | ), 194 | ], 195 | ); 196 | }); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /lib/screen/resume_maker/tabs/reference_tab.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:resumepad/model/resume_model.dart'; 3 | import 'package:resumepad/screen/add_reference/add_reference_dialog.dart'; 4 | import 'package:resumepad/screen/resume_maker/bloc/bloc_provider.dart'; 5 | import 'package:resumepad/screen/resume_maker/widget/onboarding_header_text.dart'; 6 | import 'package:resumepad/utility/color_utility.dart'; 7 | 8 | class ReferenceTab extends StatefulWidget { 9 | @override 10 | _ReferenceTabState createState() => _ReferenceTabState(); 11 | } 12 | 13 | class _ReferenceTabState extends State { 14 | @override 15 | Widget build(BuildContext context) { 16 | return Container( 17 | margin: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.18), 18 | child: _buildList(), 19 | ); 20 | } 21 | 22 | Widget _buildList() { 23 | return Padding( 24 | padding: const EdgeInsets.all(8.0), 25 | child: Column( 26 | mainAxisAlignment: MainAxisAlignment.start, 27 | crossAxisAlignment: CrossAxisAlignment.start, 28 | children: [ 29 | Row( 30 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 31 | children: [ 32 | OnBoardingHeaderText(text: 'Reference'), 33 | Padding( 34 | padding: const EdgeInsets.all(16.0), 35 | child: ClipOval( 36 | child: Container( 37 | height: 32, 38 | width: 32, 39 | color: Theme.of(context).primaryColor, 40 | child: IconButton( 41 | padding: EdgeInsets.all(4), 42 | color: Colors.white, 43 | onPressed: () => _showAddReferenceDialog(null, 0), 44 | icon: const Icon(Icons.add), 45 | )), 46 | ), 47 | ), 48 | ], 49 | ), 50 | Expanded(child: _buildReferenceList()), 51 | ], 52 | ), 53 | ); 54 | } 55 | 56 | Widget _buildReferenceList() { 57 | final _bloc = ResumeMakerBlocProvider.of(context); 58 | return Padding( 59 | padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), 60 | child: StreamBuilder( 61 | stream: _bloc.referenceListModifiedStream, 62 | builder: (context, snapshot) { 63 | return _bloc.referenceList.isEmpty 64 | ? Container( 65 | child: Text('No reference added'), 66 | ) 67 | : ListView.builder( 68 | itemBuilder: (BuildContext context, int index) { 69 | return _bloc.referenceList[index] != null 70 | ? _ReferenceCard( 71 | reference: _bloc.referenceList[index], 72 | onTap: () => _showAddReferenceDialog( 73 | _bloc.referenceList[index], 74 | index, 75 | ), 76 | ) 77 | : Offstage(); 78 | }, 79 | itemCount: _bloc.referenceList.length, 80 | ); 81 | }), 82 | ); 83 | } 84 | 85 | Future _showAddReferenceDialog(Reference tempReference, int index) async { 86 | Reference reference = await Navigator.of(context).push( 87 | MaterialPageRoute( 88 | builder: (BuildContext context) { 89 | return AddReferenceDialog( 90 | reference: tempReference, 91 | ); 92 | }, 93 | fullscreenDialog: true), 94 | ); 95 | 96 | if(reference != null){ 97 | final _bloc = ResumeMakerBlocProvider.of(context); 98 | if (tempReference != null) { 99 | _bloc.referenceList[index] = reference; 100 | } else { 101 | _bloc.referenceList.add(reference); 102 | } 103 | 104 | _bloc.referenceList.isNotEmpty ? _bloc.nextButtonEnableSink.add(true) : _bloc.nextButtonEnableSink.add(false); 105 | _bloc.referenceListModifiedSink.add(reference); 106 | } 107 | } 108 | } 109 | 110 | class _ReferenceCard extends StatelessWidget { 111 | final Reference reference; 112 | final Function onTap; 113 | 114 | _ReferenceCard({ 115 | @required this.reference, 116 | @required this.onTap, 117 | }); 118 | 119 | @override 120 | Widget build(BuildContext context) { 121 | return Dismissible( 122 | key: Key(reference.toString()), 123 | background: new Container( 124 | padding: EdgeInsets.only(right: 20.0), 125 | color: Colors.red, 126 | child: new Align( 127 | alignment: Alignment.centerRight, 128 | child: new Text('Delete', textAlign: TextAlign.right, style: new TextStyle(color: Colors.white)), 129 | )), 130 | direction: DismissDirection.endToStart, 131 | onDismissed: (direction) { 132 | final _bloc = ResumeMakerBlocProvider.of(context); 133 | _bloc.referenceList.remove(reference); 134 | _bloc.referenceListModifiedSink.add(reference); 135 | _bloc.referenceList.isNotEmpty ? _bloc.nextButtonEnableSink.add(true) : _bloc.nextButtonEnableSink.add(false); 136 | }, 137 | child: _tapableContent(context), 138 | ); 139 | } 140 | 141 | _tapableContent(BuildContext context) => Container( 142 | width: double.infinity, 143 | child: InkWell( 144 | onTap: onTap, 145 | child: Card( 146 | child: Padding( 147 | padding: const EdgeInsets.all(16.0), 148 | child: Column( 149 | mainAxisSize: MainAxisSize.max, 150 | mainAxisAlignment: MainAxisAlignment.start, 151 | crossAxisAlignment: CrossAxisAlignment.start, 152 | children: [ 153 | Text( 154 | '${reference != null ? reference.name : ''}', 155 | style: TextStyle( 156 | color: Color(getColorHexFromStr(TEXT_COLOR_BLACK)), 157 | fontSize: MediaQuery.of(context).size.shortestSide * 0.05, 158 | letterSpacing: 0.8), 159 | softWrap: true, 160 | textAlign: TextAlign.left, 161 | ), 162 | SizedBox( 163 | height: 4, 164 | ), 165 | Text( 166 | reference != null ? '${reference.designation} at ${reference.company}' : '', 167 | style: TextStyle( 168 | color: Color(getColorHexFromStr(TEXT_COLOR_BLACK)), 169 | fontSize: MediaQuery.of(context).size.shortestSide * 0.04, 170 | fontWeight: FontWeight.w500, 171 | letterSpacing: 0.8), 172 | softWrap: true, 173 | textAlign: TextAlign.left, 174 | ), 175 | SizedBox( 176 | height: 4, 177 | ), 178 | Text( 179 | reference != null ? reference.email : '', 180 | style: TextStyle( 181 | color: Color(getColorHexFromStr(TEXT_COLOR_BLACK)), 182 | fontSize: MediaQuery.of(context).size.shortestSide * 0.04, 183 | fontWeight: FontWeight.w500, 184 | letterSpacing: 0.8), 185 | softWrap: true, 186 | textAlign: TextAlign.left, 187 | ), 188 | ], 189 | ), 190 | ), 191 | ), 192 | ), 193 | ); 194 | } 195 | -------------------------------------------------------------------------------- /lib/screen/resume_maker/tabs/projects_tab.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:intl/intl.dart'; 3 | import 'package:resumepad/model/resume_model.dart'; 4 | import 'package:resumepad/screen/add_project/add_project_dialog.dart'; 5 | import 'package:resumepad/screen/resume_maker/bloc/bloc_provider.dart'; 6 | import 'package:resumepad/screen/resume_maker/widget/onboarding_header_text.dart'; 7 | import 'package:resumepad/utility/color_utility.dart'; 8 | 9 | class ProjectTab extends StatefulWidget { 10 | @override 11 | _ProjectTabState createState() => _ProjectTabState(); 12 | } 13 | 14 | class _ProjectTabState extends State { 15 | @override 16 | Widget build(BuildContext context) { 17 | return Container( 18 | margin: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.18), 19 | child: _buildList(), 20 | ); 21 | } 22 | 23 | Widget _buildList() { 24 | return Padding( 25 | padding: const EdgeInsets.all(8.0), 26 | child: Column( 27 | mainAxisAlignment: MainAxisAlignment.start, 28 | crossAxisAlignment: CrossAxisAlignment.start, 29 | children: [ 30 | Row( 31 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 32 | children: [ 33 | OnBoardingHeaderText(text: 'Project'), 34 | Padding( 35 | padding: const EdgeInsets.all(16.0), 36 | child: ClipOval( 37 | child: Container( 38 | height: 32, 39 | width: 32, 40 | color: Theme.of(context).primaryColor, 41 | child: IconButton( 42 | color: Colors.white, 43 | padding: EdgeInsets.all(4), 44 | onPressed: () => _showAddProjectDialog(null, 0), 45 | icon: const Icon(Icons.add), 46 | )), 47 | ), 48 | ), 49 | ], 50 | ), 51 | Expanded(child: _buildProjectList()), 52 | ], 53 | ), 54 | ); 55 | } 56 | 57 | Widget _buildProjectList() { 58 | final _bloc = ResumeMakerBlocProvider.of(context); 59 | return Padding( 60 | padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), 61 | child: StreamBuilder( 62 | stream: _bloc.projectListModifiedStream, 63 | builder: (context, snapshot) { 64 | return _bloc.projectList.isEmpty 65 | ? Container( 66 | child: Text('No project added'), 67 | ) 68 | : ListView.builder( 69 | itemBuilder: (BuildContext context, int index) { 70 | return _ProjectCard( 71 | project: _bloc.projectList[index], 72 | onTap: () => _showAddProjectDialog( 73 | _bloc.projectList[index], 74 | index, 75 | ), 76 | ); 77 | }, 78 | itemCount: _bloc.projectList.length, 79 | ); 80 | }), 81 | ); 82 | } 83 | 84 | Future _showAddProjectDialog(Project tempProject, int index) async { 85 | Project project = await Navigator.of(context).push( 86 | MaterialPageRoute( 87 | builder: (BuildContext context) { 88 | return AddProjectDialog( 89 | project: tempProject, 90 | ); 91 | }, 92 | fullscreenDialog: true), 93 | ); 94 | 95 | if( project != null) { 96 | final _bloc = ResumeMakerBlocProvider.of(context); 97 | if (tempProject != null) { 98 | _bloc.projectList[index] = project; 99 | } else { 100 | _bloc.projectList.add(project); 101 | } 102 | 103 | _bloc.projectList.isNotEmpty ? _bloc.nextButtonEnableSink.add(true) : _bloc.nextButtonEnableSink.add(false); 104 | _bloc.projectListModifiedSink.add(project); 105 | } 106 | } 107 | } 108 | 109 | class _ProjectCard extends StatelessWidget { 110 | final Project project; 111 | final Function onTap; 112 | 113 | _ProjectCard({ 114 | @required this.project, 115 | @required this.onTap, 116 | }); 117 | 118 | @override 119 | Widget build(BuildContext context) { 120 | return Dismissible( 121 | key: Key(project.toString()), 122 | background: new Container( 123 | padding: EdgeInsets.only(right: 20.0), 124 | color: Colors.red, 125 | child: new Align( 126 | alignment: Alignment.centerRight, 127 | child: new Text('Delete', textAlign: TextAlign.right, style: new TextStyle(color: Colors.white)), 128 | )), 129 | direction: DismissDirection.endToStart, 130 | onDismissed: (direction) { 131 | final _bloc = ResumeMakerBlocProvider.of(context); 132 | _bloc.projectList.remove(project); 133 | _bloc.projectListModifiedSink.add(project); 134 | _bloc.projectList.isNotEmpty ? _bloc.nextButtonEnableSink.add(true) : _bloc.nextButtonEnableSink.add(false); 135 | }, 136 | child: _tapableContent(context), 137 | ); 138 | } 139 | 140 | _tapableContent(BuildContext context) => InkWell( 141 | onTap: onTap, 142 | child: Card( 143 | child: Padding( 144 | padding: const EdgeInsets.all(16.0), 145 | child: Column( 146 | mainAxisAlignment: MainAxisAlignment.start, 147 | crossAxisAlignment: CrossAxisAlignment.start, 148 | children: [ 149 | Text( 150 | '${project != null ? project.projectName : ''}', 151 | style: TextStyle( 152 | color: Color(getColorHexFromStr(TEXT_COLOR_BLACK)), 153 | fontSize: MediaQuery.of(context).size.shortestSide * 0.05, 154 | letterSpacing: 0.8), 155 | softWrap: true, 156 | textAlign: TextAlign.left, 157 | ), 158 | SizedBox( 159 | height: 4, 160 | ), 161 | Text( 162 | project != null ? project.projectSummary : '', 163 | style: TextStyle( 164 | color: Color(getColorHexFromStr(TEXT_COLOR_BLACK)), 165 | fontSize: MediaQuery.of(context).size.shortestSide * 0.04, 166 | fontWeight: FontWeight.w500, 167 | letterSpacing: 0.8), 168 | softWrap: true, 169 | textAlign: TextAlign.left, 170 | ), 171 | SizedBox( 172 | height: 4, 173 | ), 174 | Row( 175 | children: [ 176 | Text( 177 | '${DateFormat.yMMM().format(project.startDate)} - ', 178 | style: TextStyle( 179 | color: Color(getColorHexFromStr(TEXT_COLOR_BLACK)), 180 | fontSize: MediaQuery.of(context).size.shortestSide * 0.03, 181 | letterSpacing: 0.8), 182 | softWrap: true, 183 | textAlign: TextAlign.left, 184 | ), 185 | Text( 186 | project.endDate.difference(DateTime.now()).inDays == 0 ? 'Present': DateFormat.yMMM().format(project.endDate), 187 | style: TextStyle( 188 | color: Color(getColorHexFromStr(TEXT_COLOR_BLACK)), 189 | fontSize: MediaQuery.of(context).size.shortestSide * 0.03, 190 | letterSpacing: 0.8), 191 | softWrap: true, 192 | textAlign: TextAlign.left, 193 | ) 194 | ], 195 | ), 196 | ], 197 | ), 198 | ), 199 | ), 200 | ); 201 | } 202 | -------------------------------------------------------------------------------- /lib/screen/resume_maker/tabs/experience_tab.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:intl/intl.dart'; 3 | import 'package:resumepad/model/resume_model.dart'; 4 | import 'package:resumepad/screen/add_experience/add_experience_dialog.dart'; 5 | import 'package:resumepad/screen/resume_maker/bloc/bloc_provider.dart'; 6 | import 'package:resumepad/screen/resume_maker/widget/onboarding_header_text.dart'; 7 | import 'package:resumepad/utility/color_utility.dart'; 8 | 9 | class ExperienceTab extends StatefulWidget { 10 | @override 11 | _ExperienceTabState createState() => _ExperienceTabState(); 12 | } 13 | 14 | class _ExperienceTabState extends State { 15 | @override 16 | Widget build(BuildContext context) { 17 | return Container( 18 | margin: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.18), 19 | child: _buildList(), 20 | ); 21 | } 22 | 23 | Widget _buildList() { 24 | return Padding( 25 | padding: const EdgeInsets.all(8.0), 26 | child: Column( 27 | mainAxisAlignment: MainAxisAlignment.start, 28 | crossAxisAlignment: CrossAxisAlignment.start, 29 | children: [ 30 | Row( 31 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 32 | children: [ 33 | OnBoardingHeaderText(text: 'Experience'), 34 | Padding( 35 | padding: const EdgeInsets.all(16.0), 36 | child: ClipOval( 37 | child: Container( 38 | height: 32, 39 | width: 32, 40 | color: Theme.of(context).primaryColor, 41 | child: IconButton( 42 | padding: EdgeInsets.all(4), 43 | color: Colors.white, 44 | onPressed: () => _showAddExperienceDialog(null, 0), 45 | icon: const Icon(Icons.add), 46 | )), 47 | ), 48 | ), 49 | ], 50 | ), 51 | Expanded(child: _buildEducationList()), 52 | ], 53 | ), 54 | ); 55 | } 56 | 57 | Widget _buildEducationList() { 58 | final _bloc = ResumeMakerBlocProvider.of(context); 59 | return Padding( 60 | padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), 61 | child: StreamBuilder( 62 | stream: _bloc.experienceListModifiedStream, 63 | builder: (context, snapshot) { 64 | return _bloc.experienceList.isEmpty 65 | ? Container( 66 | child: Text('No experience added'), 67 | ) 68 | : ListView.builder( 69 | itemBuilder: (BuildContext context, int index) { 70 | return _ExperienceCard( 71 | experience: _bloc.experienceList[index], 72 | onTap: () => _showAddExperienceDialog( 73 | _bloc.experienceList[index], 74 | index, 75 | ), 76 | ); 77 | }, 78 | itemCount: _bloc.experienceList.length, 79 | ); 80 | }), 81 | ); 82 | } 83 | 84 | Future _showAddExperienceDialog(Experience tempExperience, int index) async { 85 | Experience experience = await Navigator.of(context).push( 86 | MaterialPageRoute( 87 | builder: (BuildContext context) { 88 | return AddExperienceDialog( 89 | experience: tempExperience, 90 | ); 91 | }, 92 | fullscreenDialog: true), 93 | ); 94 | 95 | if(experience == null) return; 96 | 97 | final _bloc = ResumeMakerBlocProvider.of(context); 98 | if (tempExperience != null) { 99 | _bloc.experienceList[index] = experience; 100 | } else { 101 | _bloc.experienceList.add(experience); 102 | } 103 | 104 | _bloc.experienceList.isNotEmpty ? _bloc.nextButtonEnableSink.add(true) : _bloc.nextButtonEnableSink.add(false); 105 | _bloc.experienceListModifiedSink.add(experience); 106 | } 107 | } 108 | 109 | class _ExperienceCard extends StatelessWidget { 110 | final Experience experience; 111 | final Function onTap; 112 | 113 | _ExperienceCard({@required this.experience, this.onTap}); 114 | 115 | @override 116 | Widget build(BuildContext context) { 117 | return Dismissible( 118 | key: Key(experience.toString()), 119 | background: new Container( 120 | padding: EdgeInsets.only(right: 20.0), 121 | color: Colors.red, 122 | child: new Align( 123 | alignment: Alignment.centerRight, 124 | child: new Text('Delete', textAlign: TextAlign.right, style: new TextStyle(color: Colors.white)), 125 | )), 126 | direction: DismissDirection.endToStart, 127 | onDismissed: (direction) { 128 | final _bloc = ResumeMakerBlocProvider.of(context); 129 | _bloc.experienceList.remove(experience); 130 | _bloc.experienceListModifiedSink.add(experience); 131 | _bloc.experienceList.isNotEmpty ? _bloc.nextButtonEnableSink.add(true) : _bloc.nextButtonEnableSink.add(false); 132 | }, 133 | child: _tapableContent(context), 134 | ); 135 | } 136 | 137 | _tapableContent(BuildContext context) => InkWell( 138 | onTap: onTap, 139 | child: Card( 140 | child: Padding( 141 | padding: const EdgeInsets.all(16.0), 142 | child: Column( 143 | mainAxisAlignment: MainAxisAlignment.start, 144 | crossAxisAlignment: CrossAxisAlignment.start, 145 | children: [ 146 | Text( 147 | '${experience != null ? experience.companyName : ''}', 148 | style: TextStyle( 149 | color: Color(getColorHexFromStr(TEXT_COLOR_BLACK)), 150 | fontSize: MediaQuery.of(context).size.shortestSide * 0.05, 151 | letterSpacing: 0.8), 152 | softWrap: true, 153 | textAlign: TextAlign.left, 154 | ), 155 | SizedBox( 156 | height: 4, 157 | ), 158 | Text( 159 | experience != null ? experience.summary : '', 160 | style: TextStyle( 161 | color: Color(getColorHexFromStr(TEXT_COLOR_BLACK)), 162 | fontSize: MediaQuery.of(context).size.shortestSide * 0.04, 163 | fontWeight: FontWeight.w500, 164 | letterSpacing: 0.8), 165 | softWrap: true, 166 | textAlign: TextAlign.left, 167 | ), 168 | SizedBox( 169 | height: 4, 170 | ), 171 | Row( 172 | children: [ 173 | Text( 174 | '${DateFormat.yMMM().format(experience.startDate)} - ', 175 | style: TextStyle( 176 | color: Color(getColorHexFromStr(TEXT_COLOR_BLACK)), 177 | fontSize: MediaQuery.of(context).size.shortestSide * 0.03, 178 | letterSpacing: 0.8), 179 | softWrap: true, 180 | textAlign: TextAlign.left, 181 | ), 182 | Text( 183 | experience.endDate.difference(DateTime.now()).inDays == 0 ? 'Present': DateFormat.yMMM().format(experience.endDate), 184 | style: TextStyle( 185 | color: Color(getColorHexFromStr(TEXT_COLOR_BLACK)), 186 | fontSize: MediaQuery.of(context).size.shortestSide * 0.03, 187 | letterSpacing: 0.8), 188 | softWrap: true, 189 | textAlign: TextAlign.left, 190 | ) 191 | ], 192 | ), 193 | ], 194 | ), 195 | ), 196 | ), 197 | ); 198 | } 199 | -------------------------------------------------------------------------------- /lib/screen/resume_maker/tabs/education_tab.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:intl/intl.dart'; 3 | import 'package:resumepad/model/resume_model.dart'; 4 | import 'package:resumepad/screen/resume_maker/bloc/bloc_provider.dart'; 5 | import 'package:resumepad/screen/add_education/add_education_dialog.dart'; 6 | import 'package:resumepad/screen/resume_maker/widget/onboarding_header_text.dart'; 7 | import 'package:resumepad/utility/color_utility.dart'; 8 | 9 | class EducationTab extends StatefulWidget { 10 | @override 11 | _EducationTabState createState() => _EducationTabState(); 12 | } 13 | 14 | class _EducationTabState extends State { 15 | @override 16 | Widget build(BuildContext context) { 17 | return Container( 18 | margin: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.18), 19 | child: _buildList(), 20 | ); 21 | } 22 | 23 | Widget _buildList() { 24 | return Padding( 25 | padding: const EdgeInsets.all(8.0), 26 | child: Column( 27 | mainAxisAlignment: MainAxisAlignment.start, 28 | crossAxisAlignment: CrossAxisAlignment.start, 29 | children: [ 30 | Row( 31 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 32 | children: [ 33 | OnBoardingHeaderText(text: 'Education'), 34 | Padding( 35 | padding: const EdgeInsets.all(16.0), 36 | child: ClipOval( 37 | child: Container( 38 | height: 32, 39 | width: 32, 40 | color: Theme.of(context).primaryColor, 41 | child: IconButton( 42 | padding: EdgeInsets.all(4), 43 | color: Colors.white, 44 | onPressed: () => _showAddEducationDialog(null, 0), 45 | icon: const Icon(Icons.add), 46 | )), 47 | ), 48 | ), 49 | ], 50 | ), 51 | Expanded(child: _buildEducationList()), 52 | ], 53 | ), 54 | ); 55 | } 56 | 57 | Widget _buildEducationList() { 58 | final _bloc = ResumeMakerBlocProvider.of(context); 59 | return Padding( 60 | padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), 61 | child: StreamBuilder( 62 | stream: _bloc.educationListModifiedStream, 63 | builder: (context, snapshot) { 64 | return _bloc.educationList.isEmpty 65 | ? Container( 66 | child: Text('No educartion added'), 67 | ) 68 | : ListView.builder( 69 | itemBuilder: (BuildContext context, int index) { 70 | return _bloc.educationList[index] != null ? _EducationCard( 71 | education: _bloc.educationList[index], 72 | onTap: () => _showAddEducationDialog( 73 | _bloc.educationList[index], 74 | index, 75 | ), 76 | ): Offstage(); 77 | }, 78 | itemCount: _bloc.educationList.length, 79 | ); 80 | }), 81 | ); 82 | } 83 | 84 | Future _showAddEducationDialog(Education tempEducation, int index) async { 85 | Education education = await Navigator.of(context).push( 86 | MaterialPageRoute( 87 | builder: (BuildContext context) { 88 | return AddEducationDialog( 89 | education: tempEducation, 90 | ); 91 | }, 92 | fullscreenDialog: true), 93 | ); 94 | 95 | if(education == null) return; 96 | 97 | final _bloc = ResumeMakerBlocProvider.of(context); 98 | if(tempEducation != null) { 99 | _bloc.educationList[index] = education; 100 | } else { 101 | _bloc.educationList.add(education); 102 | } 103 | 104 | _bloc.educationList.isNotEmpty ? _bloc.nextButtonEnableSink.add(true) : _bloc.nextButtonEnableSink.add(false); 105 | _bloc.educationListModifiedSink.add(education); 106 | } 107 | } 108 | 109 | class _EducationCard extends StatelessWidget { 110 | final Education education; 111 | final Function onTap; 112 | 113 | _EducationCard({ 114 | @required this.education, 115 | @required this.onTap, 116 | }); 117 | 118 | @override 119 | Widget build(BuildContext context) { 120 | return Dismissible( 121 | key: Key(education.toString()), 122 | background: new Container( 123 | padding: EdgeInsets.only(right: 20.0), 124 | color: Colors.red, 125 | child: new Align( 126 | alignment: Alignment.centerRight, 127 | child: new Text('Delete', textAlign: TextAlign.right, style: new TextStyle(color: Colors.white)), 128 | )), 129 | direction: DismissDirection.endToStart, 130 | onDismissed: (direction) { 131 | final _bloc = ResumeMakerBlocProvider.of(context); 132 | _bloc.educationList.remove(education); 133 | _bloc.educationListModifiedSink.add(education); 134 | _bloc.educationList.isNotEmpty ? _bloc.nextButtonEnableSink.add(true) : _bloc.nextButtonEnableSink.add(false); 135 | }, 136 | child: _tapableContent(context), 137 | ); 138 | } 139 | 140 | _tapableContent(BuildContext context) => InkWell( 141 | onTap: onTap, 142 | child: Card( 143 | child: Padding( 144 | padding: const EdgeInsets.all(16.0), 145 | child: Column( 146 | mainAxisAlignment: MainAxisAlignment.start, 147 | crossAxisAlignment: CrossAxisAlignment.start, 148 | children: [ 149 | Text( 150 | '${education != null ? education.courseTaken: ''}', 151 | style: TextStyle( 152 | color: Color(getColorHexFromStr(TEXT_COLOR_BLACK)), 153 | fontSize: MediaQuery.of(context).size.shortestSide * 0.05, 154 | letterSpacing: 0.8), 155 | softWrap: true, 156 | textAlign: TextAlign.left, 157 | ), 158 | SizedBox( 159 | height: 4, 160 | ), 161 | Text( 162 | education != null ? education.universityName: '', 163 | style: TextStyle( 164 | color: Color(getColorHexFromStr(TEXT_COLOR_BLACK)), 165 | fontSize: MediaQuery.of(context).size.shortestSide * 0.04, 166 | fontWeight: FontWeight.w500, 167 | letterSpacing: 0.8), 168 | softWrap: true, 169 | textAlign: TextAlign.left, 170 | ), 171 | SizedBox( 172 | height: 4, 173 | ), 174 | Row( 175 | children: [ 176 | Text( 177 | '${DateFormat.yMMM().format(education.startDate)} - ', 178 | style: TextStyle( 179 | color: Color(getColorHexFromStr(TEXT_COLOR_BLACK)), 180 | fontSize: MediaQuery.of(context).size.shortestSide * 0.03, 181 | letterSpacing: 0.8), 182 | softWrap: true, 183 | textAlign: TextAlign.left, 184 | ), 185 | Text( 186 | education.endDate.difference(DateTime.now()).inDays == 0 ? 'Present': DateFormat.yMMM().format(education.endDate), 187 | style: TextStyle( 188 | color: Color(getColorHexFromStr(TEXT_COLOR_BLACK)), 189 | fontSize: MediaQuery.of(context).size.shortestSide * 0.03, 190 | letterSpacing: 0.8), 191 | softWrap: true, 192 | textAlign: TextAlign.left, 193 | ) 194 | ], 195 | ), 196 | ], 197 | ), 198 | ), 199 | ), 200 | ); 201 | } 202 | -------------------------------------------------------------------------------- /lib/screen/resume_maker/tabs/contact_tab.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:resumepad/model/resume_model.dart'; 4 | import 'package:resumepad/screen/resume_maker/bloc/bloc_provider.dart'; 5 | import 'package:resumepad/screen/resume_maker/widget/onboarding_header_text.dart'; 6 | import 'package:resumepad/widget/country_code_picker.dart'; 7 | import 'package:resumepad/widget/custom_text_feild_form.dart'; 8 | import 'package:resumepad/widget/rounded_button.dart'; 9 | 10 | class ContactTab extends StatefulWidget { 11 | @override 12 | _ContactTabState createState() => _ContactTabState(); 13 | } 14 | 15 | class _ContactTabState extends State { 16 | final TextEditingController _emailController = TextEditingController(); 17 | final TextEditingController _codeController = TextEditingController(); 18 | final TextEditingController _phoneController = TextEditingController(); 19 | final TextEditingController _linkedController = TextEditingController(); 20 | final _formKey = GlobalKey(); 21 | 22 | @override 23 | void didChangeDependencies() { 24 | super.didChangeDependencies(); 25 | final _bloc = ResumeMakerBlocProvider.of(context); 26 | _emailController.text = _bloc.contact != null ? _bloc.contact.email : _emailController.text; 27 | _emailController..addListener(_onEmailChange); 28 | 29 | _phoneController.text = _bloc.contact != null ? _bloc.contact.phone : _phoneController.text; 30 | _phoneController..addListener(_onPhoneChange); 31 | 32 | _linkedController.text = _bloc.contact != null ? _bloc.contact.linkedin : _linkedController.text; 33 | _linkedController..addListener(_onLinkedInChange); 34 | 35 | _codeController.text = _bloc.contact != null ? _bloc.contact.countryCode : _codeController.text; 36 | _codeController..addListener(_onCodeChnage); 37 | } 38 | 39 | void _onEmailChange() { 40 | final _bloc = ResumeMakerBlocProvider.of(context); 41 | if (_bloc.contact != null && _emailController.text != _bloc.contact.email) { 42 | _bloc.enableSaveContactButton(true); 43 | } 44 | } 45 | 46 | void _onPhoneChange() { 47 | final _bloc = ResumeMakerBlocProvider.of(context); 48 | if (_bloc.contact != null && _phoneController.text != _bloc.contact.phone) { 49 | _bloc.enableSaveContactButton(true); 50 | } 51 | } 52 | 53 | void _onLinkedInChange() { 54 | final _bloc = ResumeMakerBlocProvider.of(context); 55 | if (_bloc.contact != null && _linkedController.text != _bloc.contact.linkedin) { 56 | _bloc.enableSaveContactButton(true); 57 | } 58 | } 59 | 60 | void _onCodeChnage() { 61 | final _bloc = ResumeMakerBlocProvider.of(context); 62 | if (_bloc.contact != null && _codeController.text != _bloc.contact.countryCode) { 63 | _bloc.enableSaveContactButton(true); 64 | } 65 | } 66 | 67 | 68 | @override 69 | void dispose() { 70 | super.dispose(); 71 | _emailController.dispose(); 72 | _phoneController.dispose(); 73 | _linkedController.dispose(); 74 | } 75 | 76 | @override 77 | Widget build(BuildContext context) { 78 | return Container( 79 | margin: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.18), 80 | child: _buildForm(), 81 | ); 82 | } 83 | 84 | Widget _buildForm() { 85 | final _bloc = ResumeMakerBlocProvider.of(context); 86 | return SingleChildScrollView( 87 | child: Form( 88 | key: _formKey, 89 | child: Padding( 90 | padding: const EdgeInsets.all(8.0), 91 | child: Column( 92 | mainAxisAlignment: MainAxisAlignment.start, 93 | crossAxisAlignment: CrossAxisAlignment.start, 94 | children: [ 95 | OnBoardingHeaderText(text: 'Contact'), 96 | Padding( 97 | padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), 98 | child: CustomTextFieldForm( 99 | controller: _emailController, 100 | hintText: 'Whats is your email?', 101 | helperText: 'Your email', 102 | textInputType: TextInputType.emailAddress, 103 | validator: (val) { 104 | Pattern pattern = 105 | r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$'; 106 | RegExp regex = new RegExp(pattern); 107 | if (!regex.hasMatch(val)) 108 | return 'Enter Valid Email'; 109 | else 110 | return null; 111 | }, 112 | ), 113 | ), 114 | Row( 115 | mainAxisSize: MainAxisSize.max, 116 | children: [ 117 | InkWell( 118 | onTap: _showCountryDialog, 119 | child: Padding( 120 | padding: const EdgeInsets.only(left: 16.0, top: 16.0, bottom: 16), 121 | child: IgnorePointer( 122 | child: Container( 123 | width: 75, 124 | child: CustomTextFieldForm( 125 | controller: _codeController, 126 | hintText: '( )', 127 | helperText: 'code', 128 | maxLength: 8, 129 | textInputType: TextInputType.phone, 130 | readOnly: true, 131 | validator: (val) => val.length == 0 ? 'Code' : null, 132 | ), 133 | ), 134 | ), 135 | ), 136 | ), 137 | Flexible( 138 | child: Padding( 139 | padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), 140 | child: CustomTextFieldForm( 141 | controller: _phoneController, 142 | hintText: 'Enter mobile digits?', 143 | helperText: 'Your mobile number', 144 | textInputType: TextInputType.phone, 145 | validator: (val) => 146 | val.length == 0 ? 'Empty mobile number' : val.length < 9 ? 'Invalid mobile number' : null, 147 | inputFormatter: [ 148 | LengthLimitingTextInputFormatter(10), 149 | ]), 150 | ), 151 | ), 152 | ], 153 | ), 154 | Padding( 155 | padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), 156 | child: CustomTextFieldForm( 157 | controller: _linkedController, 158 | hintText: 'Enter your LinkedIn profile', 159 | helperText: 'Your LinkedIn', 160 | validator: (val) => val.length == 0 ? 'Empty link' : null, 161 | ), 162 | ), 163 | SizedBox( 164 | height: 24, 165 | ), 166 | StreamBuilder( 167 | stream: _bloc.saveContactButtonEnableStream, 168 | builder: (context, snapshot) { 169 | bool isEnable = snapshot.hasData ? snapshot.data : _bloc.contact != null ? false : true; 170 | return RoundedButton( 171 | text: 'Save', 172 | onPressed: isEnable ? _onAddContact : null, 173 | ); 174 | }), 175 | ], 176 | ), 177 | ), 178 | ), 179 | ); 180 | } 181 | 182 | void _onAddContact() { 183 | FocusScope.of(context).requestFocus(new FocusNode()); 184 | final _bloc = ResumeMakerBlocProvider.of(context); 185 | if (_formKey.currentState.validate()) { 186 | _formKey.currentState.save(); 187 | _bloc.contact = Contact( 188 | email: _emailController.text, 189 | phone: _phoneController.text, 190 | linkedin: _linkedController.text, 191 | countryCode: _codeController.text); 192 | _bloc.enableSaveContactButton(false); 193 | } 194 | } 195 | 196 | void _showCountryDialog() { 197 | showDialog( 198 | context: context, 199 | builder: (_) => SelectionDialog(), 200 | ).then((e) { 201 | if (e != null) { 202 | _codeController.text = e.toString(); 203 | } 204 | }); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /lib/model/resume_model.dart: -------------------------------------------------------------------------------- 1 | class Profile { 2 | String name; 3 | String designation; 4 | String imagePath; 5 | String currentCityAndCountry; 6 | 7 | Profile({ 8 | this.name, 9 | this.designation, 10 | this.imagePath, 11 | this.currentCityAndCountry, 12 | }); 13 | 14 | factory Profile.fromJson(Map json) => new Profile( 15 | name: json["name"], 16 | designation: json["designation"], 17 | imagePath: json["imagePath"], 18 | currentCityAndCountry: json["currentCityAndCountry"], 19 | ); 20 | 21 | Map toJson() => { 22 | "name": name, 23 | "designation": designation, 24 | "imagePath": imagePath, 25 | "currentCityAndCountry": currentCityAndCountry, 26 | }; 27 | } 28 | 29 | class Contact { 30 | String email; 31 | String phone; 32 | String countryCode; 33 | String linkedin; 34 | 35 | Contact({ 36 | this.email, 37 | this.phone, 38 | this.linkedin, 39 | this.countryCode, 40 | }); 41 | 42 | factory Contact.fromJson(Map json) => new Contact( 43 | email: json["email"], 44 | phone: json["phone"], 45 | linkedin: json["linkedin"], 46 | countryCode: json["countryCode"], 47 | ); 48 | 49 | Map toJson() => { 50 | "email": email, 51 | "phone": phone, 52 | "linkedin": linkedin, 53 | "countryCode": countryCode, 54 | }; 55 | } 56 | 57 | class Experience { 58 | String companyName; 59 | String designation; 60 | DateTime startDate; 61 | DateTime endDate; 62 | String summary; 63 | String companyLink; 64 | 65 | Experience({ 66 | this.companyName, 67 | this.designation, 68 | this.startDate, 69 | this.endDate, 70 | this.summary, 71 | this.companyLink, 72 | }); 73 | 74 | factory Experience.fromJson(Map json) => new Experience( 75 | companyName: json["companyName"], 76 | designation: json["designation"], 77 | startDate: json["startDate"], 78 | endDate: json["endDate"], 79 | summary: json["summary"], 80 | companyLink: json["companyLink"], 81 | ); 82 | 83 | Map toJson() => { 84 | "companyName": companyName, 85 | "designation": designation, 86 | "startDate": startDate, 87 | "endDate": endDate, 88 | "summary": summary, 89 | "companyLink": companyLink, 90 | }; 91 | } 92 | 93 | class Project { 94 | String projectName; 95 | DateTime startDate; 96 | DateTime endDate; 97 | String projectSummary; 98 | String projectLink; 99 | 100 | Project({ 101 | this.projectName, 102 | this.startDate, 103 | this.endDate, 104 | this.projectSummary, 105 | this.projectLink, 106 | }); 107 | 108 | factory Project.fromJson(Map json) => new Project( 109 | projectName: json["projectName"], 110 | startDate: json["startDate"], 111 | endDate: json["endDate"], 112 | projectSummary: json["projectSummary"], 113 | projectLink: json["projectLink"], 114 | ); 115 | 116 | Map toJson() => { 117 | "projectName": projectName, 118 | "startDate": startDate, 119 | "endDate": endDate, 120 | "projectSummary": projectSummary, 121 | "projectLink": projectLink, 122 | }; 123 | } 124 | 125 | class Course { 126 | String courseName; 127 | DateTime startDate; 128 | DateTime endDate; 129 | String courseSummary; 130 | String courseLink; 131 | bool status; 132 | 133 | Course({ 134 | this.courseName, 135 | this.startDate, 136 | this.endDate, 137 | this.courseSummary, 138 | this.courseLink, 139 | this.status, 140 | }); 141 | 142 | factory Course.fromJson(Map json) => new Course( 143 | courseName: json["courseName"], 144 | startDate: json["startDate"], 145 | endDate: json["endDate"], 146 | courseSummary: json["courseSummary"], 147 | courseLink: json["courseLink"], 148 | status: json["status"], 149 | ); 150 | 151 | Map toJson() => { 152 | "courseName": courseName, 153 | "startDate": startDate, 154 | "endDate": endDate, 155 | "courseSummary": courseSummary, 156 | "courseLink": courseLink, 157 | "status": status, 158 | }; 159 | } 160 | 161 | class Education { 162 | String universityName; 163 | DateTime startDate; 164 | DateTime endDate; 165 | String courseTaken; 166 | String collegeLink; 167 | 168 | Education({ 169 | this.universityName, 170 | this.startDate, 171 | this.endDate, 172 | this.courseTaken, 173 | this.collegeLink, 174 | }); 175 | 176 | factory Education.fromJson(Map json) => new Education( 177 | universityName: json["universityName"], 178 | startDate: json["startDate"], 179 | endDate: json["endDate"], 180 | courseTaken: json["courseTaken"], 181 | collegeLink: json["collegeLink"], 182 | ); 183 | 184 | Map toJson() => { 185 | "universityName": universityName, 186 | "startDate": startDate, 187 | "endDate": endDate, 188 | "courseTaken": courseTaken, 189 | "collegeLink": collegeLink, 190 | }; 191 | } 192 | 193 | class Reference { 194 | String name; 195 | String designation; 196 | String company; 197 | String email; 198 | 199 | Reference({ 200 | this.name, 201 | this.designation, 202 | this.company, 203 | this.email, 204 | }); 205 | 206 | factory Reference.fromJson(Map json) => new Reference( 207 | name: json["name"], 208 | designation: json["designation"], 209 | company: json["company"], 210 | email: json["email"], 211 | ); 212 | 213 | Map toJson() => { 214 | "name": name, 215 | "designation": designation, 216 | "company": company, 217 | "email": email, 218 | }; 219 | } 220 | 221 | class Language { 222 | String name; 223 | String level; 224 | 225 | Language({ 226 | this.name, 227 | this.level, 228 | }); 229 | 230 | factory Language.fromJson(Map json) => new Language( 231 | name: json["name"], 232 | level: json["level"], 233 | ); 234 | 235 | Map toJson() => { 236 | "name": name, 237 | "level": level, 238 | }; 239 | } 240 | 241 | class Resume { 242 | Profile profile; 243 | Contact contact; 244 | List experiences; 245 | List projects; 246 | List educations; 247 | List skills; 248 | List languages; 249 | List references; 250 | List courses; 251 | String profileSummary; 252 | 253 | Resume({ 254 | this.profile, 255 | this.contact, 256 | this.experiences, 257 | this.projects, 258 | this.educations, 259 | this.skills, 260 | this.languages, 261 | this.references, 262 | this.courses, 263 | this.profileSummary, 264 | }); 265 | 266 | factory Resume.fromJson(Map json) { 267 | var experiencesList = json['experiences'] as List; 268 | List experiences = experiencesList.map((i) => Experience.fromJson(i)).toList(); 269 | 270 | var projectsList = json['projects'] as List; 271 | List projects = projectsList.map((i) => Project.fromJson(i)).toList(); 272 | 273 | var educationsList = json['educations'] as List; 274 | List educations = educationsList.map((i) => Education.fromJson(i)).toList(); 275 | 276 | var referencesList = json['references'] as List; 277 | List references = referencesList.map((i) => Reference.fromJson(i)).toList(); 278 | 279 | var coursesList = json['courses'] as List; 280 | List courses = coursesList.map((i) => Course.fromJson(i)).toList(); 281 | 282 | var languagesList = json['languages'] as List; 283 | List languages = languagesList.map((i) => Language.fromJson(i)).toList(); 284 | 285 | return Resume( 286 | profile: Profile.fromJson(json["profile"]), 287 | contact: Contact.fromJson(json["contact"]), 288 | experiences: experiences, 289 | projects: projects, 290 | educations: educations, 291 | skills: json["companyLink"], 292 | languages: languages, 293 | references: references, 294 | courses: courses, 295 | profileSummary: json["profileSummary"], 296 | ); 297 | } 298 | 299 | Map toJson() => { 300 | "profile": profile, 301 | "contact": contact, 302 | "experiences": experiences, 303 | "projects": projects, 304 | "educations": educations, 305 | "skills": skills, 306 | "languages": languages, 307 | "references": references, 308 | "courses": courses, 309 | "profileSummary": profileSummary, 310 | }; 311 | } 312 | -------------------------------------------------------------------------------- /lib/screen/resume_maker/bloc/resume_maker_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:resumepad/model/resume_model.dart'; 5 | import 'package:resumepad/utility/constants.dart'; 6 | import 'package:resumepad/utility/create_pdf.dart'; 7 | import 'package:rxdart/rxdart.dart'; 8 | 9 | class ResumeMakerBloc { 10 | //String to store navigationId 11 | int currentPage = NAVIGATE_TO_PROFILE_TAB; 12 | List educationList = List(); 13 | List experienceList = List(); 14 | List projectList = List(); 15 | List skillList = List(); 16 | List referenceList = List(); 17 | List languageList = List(); 18 | Profile profile; 19 | File userImage; 20 | String profileSummary; 21 | Contact contact; 22 | 23 | final _mainPagerBehaviorSubject = PublishSubject(); 24 | 25 | Stream get mainPagerStream => _mainPagerBehaviorSubject.stream; 26 | 27 | Sink get mainPagerSink => _mainPagerBehaviorSubject.sink; 28 | 29 | final _selectedPageBehaviorSubject = PublishSubject(); 30 | 31 | Stream get pageNavigationStream => _selectedPageBehaviorSubject.stream; 32 | 33 | Sink get pageNavigationSink => _selectedPageBehaviorSubject.sink; 34 | 35 | final _errorBehaviorSubject = PublishSubject(); 36 | 37 | Stream get errorStream => _errorBehaviorSubject.stream.asBroadcastStream(); 38 | 39 | Sink get errorSink => _errorBehaviorSubject.sink; 40 | 41 | final _pictureClickBehaviorSubject = PublishSubject(); 42 | 43 | Stream get pictureClickStream => _pictureClickBehaviorSubject.stream.asBroadcastStream(); 44 | 45 | Sink get pictureClickSink => _pictureClickBehaviorSubject.sink; 46 | 47 | final _educationListModifiedBehaviorSubject = PublishSubject(); 48 | 49 | Stream get educationListModifiedStream => _educationListModifiedBehaviorSubject.stream.asBroadcastStream(); 50 | 51 | Sink get educationListModifiedSink => _educationListModifiedBehaviorSubject.sink; 52 | 53 | final _experienceListModifiedBehaviorSubject = PublishSubject(); 54 | 55 | Stream get experienceListModifiedStream => 56 | _experienceListModifiedBehaviorSubject.stream.asBroadcastStream(); 57 | 58 | Sink get experienceListModifiedSink => _experienceListModifiedBehaviorSubject.sink; 59 | 60 | final _projectListModifiedBehaviorSubject = PublishSubject(); 61 | 62 | Stream get projectListModifiedStream => _projectListModifiedBehaviorSubject.stream.asBroadcastStream(); 63 | 64 | Sink get projectListModifiedSink => _projectListModifiedBehaviorSubject.sink; 65 | 66 | final _skillListModifiedBehaviorSubject = PublishSubject(); 67 | 68 | Stream get skillListModifiedStream => _skillListModifiedBehaviorSubject.stream.asBroadcastStream(); 69 | 70 | Sink get skillListModifiedSink => _skillListModifiedBehaviorSubject.sink; 71 | 72 | final _referenceListModifiedBehaviorSubject = PublishSubject(); 73 | 74 | Stream get referenceListModifiedStream => _referenceListModifiedBehaviorSubject.stream.asBroadcastStream(); 75 | 76 | Sink get referenceListModifiedSink => _referenceListModifiedBehaviorSubject.sink; 77 | 78 | final _languageListModifiedBehaviorSubject = PublishSubject(); 79 | 80 | Stream get languageListModifiedStream => _languageListModifiedBehaviorSubject.stream.asBroadcastStream(); 81 | 82 | Sink get languageListModifiedSink => _languageListModifiedBehaviorSubject.sink; 83 | 84 | final _nextButtonEnableBehaviorSubject = PublishSubject(); 85 | 86 | Stream get nextButtonEnableStream => _nextButtonEnableBehaviorSubject.stream.asBroadcastStream(); 87 | 88 | Sink get nextButtonEnableSink => _nextButtonEnableBehaviorSubject.sink; 89 | 90 | final _saveProfileButtonEnableBehaviorSubject = PublishSubject(); 91 | 92 | Stream get saveProfileButtonEnableStream => _saveProfileButtonEnableBehaviorSubject.stream.asBroadcastStream(); 93 | 94 | Sink get saveProfileButtonEnableSink => _saveProfileButtonEnableBehaviorSubject.sink; 95 | 96 | final _saveProfileSummaryButtonEnableBehaviorSubject = PublishSubject(); 97 | 98 | Stream get saveProfileSummaryButtonEnableStream => 99 | _saveProfileSummaryButtonEnableBehaviorSubject.stream.asBroadcastStream(); 100 | 101 | Sink get saveProfileSummaryButtonEnableSink => _saveProfileSummaryButtonEnableBehaviorSubject.sink; 102 | 103 | final _saveContactButtonEnableBehaviorSubject = PublishSubject(); 104 | 105 | Stream get saveContactButtonEnableStream => _saveContactButtonEnableBehaviorSubject.stream.asBroadcastStream(); 106 | 107 | Sink get saveContactButtonEnableSink => _saveContactButtonEnableBehaviorSubject.sink; 108 | 109 | final _skillIconBehaviorSubject = PublishSubject(); 110 | 111 | Stream get skillIconStream => _skillIconBehaviorSubject.stream.asBroadcastStream(); 112 | 113 | Sink get skillIconSink => _skillIconBehaviorSubject.sink; 114 | 115 | final _previewButtonVisibilityBehaviorSubject = PublishSubject(); 116 | 117 | Stream get previewButtonVisibilityStream => _previewButtonVisibilityBehaviorSubject.stream.asBroadcastStream(); 118 | 119 | Sink get previewButtonVisibilitySink => _previewButtonVisibilityBehaviorSubject.sink; 120 | 121 | dispose() { 122 | _mainPagerBehaviorSubject.close(); 123 | _selectedPageBehaviorSubject.close(); 124 | _errorBehaviorSubject.close(); 125 | _pictureClickBehaviorSubject.close(); 126 | _educationListModifiedBehaviorSubject.close(); 127 | _experienceListModifiedBehaviorSubject.close(); 128 | _projectListModifiedBehaviorSubject.close(); 129 | _nextButtonEnableBehaviorSubject.close(); 130 | _saveProfileButtonEnableBehaviorSubject.close(); 131 | _saveProfileSummaryButtonEnableBehaviorSubject.close(); 132 | _saveContactButtonEnableBehaviorSubject.close(); 133 | _skillListModifiedBehaviorSubject.close(); 134 | _referenceListModifiedBehaviorSubject.close(); 135 | _languageListModifiedBehaviorSubject.close(); 136 | _skillIconBehaviorSubject.close(); 137 | _previewButtonVisibilityBehaviorSubject.close(); 138 | } 139 | 140 | navigateToNextPageIfPossible() { 141 | switch (currentPage) { 142 | case NAVIGATE_TO_PROFILE_TAB: 143 | { 144 | currentPage = NAVIGATE_TO_PROFILE_SUMMARY_TAB; 145 | profileSummary != null && profileSummary.isNotEmpty ? nextButtonEnableSink.add(true) : nextButtonEnableSink.add(false); 146 | 147 | break; 148 | } 149 | 150 | case NAVIGATE_TO_PROFILE_SUMMARY_TAB: 151 | { 152 | currentPage = NAVIGATE_TO_CONTACT_TAB; 153 | contact != null ? nextButtonEnableSink.add(true) : nextButtonEnableSink.add(false); 154 | break; 155 | } 156 | 157 | case NAVIGATE_TO_CONTACT_TAB: 158 | { 159 | currentPage = NAVIGATE_TO_EDUCATION_TAB; 160 | educationList.isNotEmpty ? nextButtonEnableSink.add(true) : nextButtonEnableSink.add(false); 161 | break; 162 | } 163 | 164 | case NAVIGATE_TO_EDUCATION_TAB: 165 | { 166 | experienceList.isNotEmpty ? nextButtonEnableSink.add(true) : nextButtonEnableSink.add(false); 167 | currentPage = NAVIGATE_TO_EXPERIENCE_TAB; 168 | break; 169 | } 170 | 171 | case NAVIGATE_TO_EXPERIENCE_TAB: 172 | { 173 | projectList.isNotEmpty ? nextButtonEnableSink.add(true) : nextButtonEnableSink.add(false); 174 | currentPage = NAVIGATE_TO_PROJECTS_TAB; 175 | break; 176 | } 177 | 178 | case NAVIGATE_TO_PROJECTS_TAB: 179 | { 180 | skillList.isNotEmpty ? nextButtonEnableSink.add(true) : nextButtonEnableSink.add(false); 181 | currentPage = NAVIGATE_TO_SKILLS_TAB; 182 | break; 183 | } 184 | 185 | case NAVIGATE_TO_SKILLS_TAB: 186 | { 187 | referenceList.isNotEmpty ? nextButtonEnableSink.add(true) : nextButtonEnableSink.add(false); 188 | currentPage = NAVIGATE_TO_REFERENCES_TAB; 189 | break; 190 | } 191 | 192 | case NAVIGATE_TO_REFERENCES_TAB: 193 | { 194 | languageList.isNotEmpty ? nextButtonEnableSink.add(true) : nextButtonEnableSink.add(false); 195 | currentPage = NAVIGATE_TO_LANGUAGE_TAB; 196 | break; 197 | } 198 | 199 | case NAVIGATE_TO_LANGUAGE_TAB: 200 | { 201 | currentPage = NAVIGATE_TO_ALL_DONE_TAB; 202 | break; 203 | } 204 | 205 | case NAVIGATE_TO_ALL_DONE_TAB: 206 | { 207 | break; 208 | } 209 | 210 | default: 211 | currentPage = NAVIGATE_TO_PROFILE_TAB; 212 | } 213 | pageNavigationSink.add(currentPage); 214 | } 215 | 216 | navigateToPreviousPageIfPossible() { 217 | nextButtonEnableSink.add(true); 218 | currentPage -= 1; 219 | pageNavigationSink.add(currentPage); 220 | } 221 | 222 | enableSaveContactButton(bool isEnable) { 223 | saveContactButtonEnableSink.add(isEnable); 224 | nextButtonEnableSink.add(!isEnable); 225 | } 226 | 227 | Future createPdf() async{ 228 | Resume resume = Resume( 229 | profile: profile, 230 | profileSummary: profileSummary, 231 | contact: contact, 232 | educations: educationList, 233 | experiences: experienceList, 234 | projects: projectList, 235 | references: referenceList, 236 | languages: languageList, 237 | skills: skillList, 238 | ); 239 | await getPdf(resume); 240 | } 241 | } 242 | --------------------------------------------------------------------------------