├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── haroldadmin │ │ └── kshitijchauhan │ │ └── resumade │ │ └── ExampleInstrumentedTest.kt │ ├── dev │ └── google-services.json │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── haroldadmin │ │ │ └── kshitijchauhan │ │ │ └── resumade │ │ │ ├── adapter │ │ │ ├── DiffUtilCallback.kt │ │ │ ├── EducationAdapter.kt │ │ │ ├── ExperienceAdapter.kt │ │ │ ├── FragmentAdapter.kt │ │ │ ├── ProjectAdapter.kt │ │ │ ├── ResumeAdapter.kt │ │ │ └── SwipeToDeleteCallback.kt │ │ │ ├── repository │ │ │ ├── LocalRepository.kt │ │ │ ├── Repository.kt │ │ │ └── database │ │ │ │ ├── Daos.kt │ │ │ │ ├── Entities.kt │ │ │ │ ├── ResumeDatabase.kt │ │ │ │ └── SingletonHolder.kt │ │ │ ├── ui │ │ │ ├── activities │ │ │ │ ├── AboutUsActivity.kt │ │ │ │ ├── CreateResumeActivity.kt │ │ │ │ ├── MainActivity.kt │ │ │ │ └── PreviewActivity.kt │ │ │ └── fragments │ │ │ │ ├── EducationFragment.kt │ │ │ │ ├── ExperienceFragment.kt │ │ │ │ ├── PersonalFragment.kt │ │ │ │ └── ProjectsFragment.kt │ │ │ ├── utilities │ │ │ ├── AppDispatchers.kt │ │ │ ├── Extensions.kt │ │ │ ├── HtmlBuilder.kt │ │ │ └── inputvalidator │ │ │ │ ├── Errors.kt │ │ │ │ ├── Extensions.kt │ │ │ │ ├── Properties.kt │ │ │ │ ├── Types.kt │ │ │ │ └── validators │ │ │ │ └── TextValidator.kt │ │ │ └── viewmodel │ │ │ ├── CreateResumeViewModel.kt │ │ │ └── MainViewModel.kt │ └── res │ │ ├── drawable │ │ ├── ic_github_circle.xml │ │ ├── ic_outline_info_24px.xml │ │ ├── ic_round_add_24px.xml │ │ ├── ic_round_code_24px.xml │ │ ├── ic_round_delete_24px.xml │ │ ├── ic_round_done_24px.xml │ │ ├── ic_round_edit_24px.xml │ │ ├── ic_round_open_in_new_24px.xml │ │ ├── ic_round_preview_24px.xml │ │ ├── ic_round_print_24px.xml │ │ ├── ic_round_rate_review_24px.xml │ │ ├── ic_round_save_24px.xml │ │ ├── list_divider.xml │ │ └── web_hi_res_512.png │ │ ├── layout │ │ ├── activity_about_us.xml │ │ ├── activity_create_resume.xml │ │ ├── activity_main.xml │ │ ├── activity_preview.xml │ │ ├── card_education.xml │ │ ├── card_experience.xml │ │ ├── card_project.xml │ │ ├── card_resume.xml │ │ ├── fragment_education.xml │ │ ├── fragment_experience.xml │ │ ├── fragment_personal.xml │ │ ├── fragment_projects.xml │ │ └── no_items_view.xml │ │ ├── menu │ │ ├── menu_create_resume_activity.xml │ │ └── menu_main_activity.xml │ │ ├── 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 │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── haroldadmin │ └── kshitijchauhan │ └── resumade │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea/ 5 | .idea/libraries 6 | .idea/modules.xml 7 | .idea/workspace.xml 8 | .idea/misc.xml 9 | .idea/vcs.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .idea/vcs.xml 15 | .idea/misc.xml 16 | # Built application files 17 | *.apk 18 | *.ap_ 19 | /app/release 20 | 21 | # Files for the ART/Dalvik VM 22 | *.dex 23 | 24 | # Java class files 25 | *.class 26 | 27 | # Generated files 28 | bin/ 29 | gen/ 30 | out/ 31 | 32 | # Gradle files 33 | .gradle/ 34 | build/ 35 | 36 | # Local configuration file (sdk path, etc) 37 | local.properties 38 | 39 | # Proguard folder generated by Eclipse 40 | proguard/ 41 | 42 | # Log Files 43 | *.log 44 | 45 | # Android Studio Navigation editor temp files 46 | .navigation/ 47 | 48 | # Android Studio captures folder 49 | captures/ 50 | 51 | # IntelliJ 52 | *.iml 53 | .idea/workspace.xml 54 | .idea/tasks.xml 55 | .idea/gradle.xml 56 | .idea/assetWizardSettings.xml 57 | .idea/dictionaries 58 | .idea/libraries 59 | .idea/caches 60 | 61 | # Keystore files 62 | # Uncomment the following line if you do not want to check your keystore files in. 63 | #*.jks 64 | 65 | # External native build folder generated in Android Studio 2.2 and later 66 | .externalNativeBuild 67 | 68 | # Google Services (e.g. APIs or Firebase) 69 | app/google-services.json 70 | 71 | # Freeline 72 | freeline.py 73 | freeline/ 74 | freeline_project_description.json 75 | 76 | # fastlane 77 | fastlane/report.xml 78 | fastlane/Preview.html 79 | fastlane/screenshots 80 | fastlane/test_output 81 | fastlane/readme.md 82 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | jdk: 3 | - oraclejdk8 4 | android: 5 | components: 6 | - tools 7 | - tools 8 | - platform-tools 9 | - build-tools-28.0.3 10 | - android-28 11 | - extra-google-google_play_services 12 | - extra-android-m2repository 13 | before_cache: 14 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 15 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 16 | cache: 17 | directories: 18 | - $HOME/.gradle/caches/ 19 | - $HOME/.gradle/wrapper/ 20 | - $HOME/.android/build-cache 21 | before_install: 22 | - yes | sdkmanager "platforms;android-28" 23 | script: 24 | # Build parameter 'dev' ensures that the google-services.json 25 | # file in app/src/dev is used 26 | - ./gradlew clean build -Pbuild=dev -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Resumade is an Android app that helps you build your resume. 2 | It's written in Kotlin and built with Android Architecture components. 3 | 4 | Any contributions are welcome! 5 | 6 | 7 | 8 | ## Notice 9 | 10 | This repository is now archived. Resumade is no longer available on Google Play. 11 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | apply plugin: 'kotlin-kapt' 8 | 9 | apply plugin: 'io.fabric' 10 | 11 | 12 | android { 13 | compileSdkVersion 28 14 | defaultConfig { 15 | applicationId "com.haroldadmin.kshitijchauhan.resumade" 16 | minSdkVersion 21 17 | targetSdkVersion 28 18 | versionCode 11 19 | versionName "2.0.1" 20 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 21 | } 22 | buildTypes { 23 | release { 24 | shrinkResources true 25 | minifyEnabled true 26 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 27 | } 28 | dev { 29 | minifyEnabled true 30 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 31 | } 32 | } 33 | } 34 | 35 | def build_param = "${build}"; 36 | 37 | if (build_param != "dev") { 38 | //exclude production build 39 | android.variantFilter { variant -> 40 | if (variant.buildType.name.equals('dev')) { 41 | variant.setIgnore(true) 42 | } 43 | } 44 | } else { 45 | //exclude all except production build 46 | android.variantFilter { variant -> 47 | if (!variant.buildType.name.equals('dev')) { 48 | variant.setIgnore(true) 49 | } 50 | } 51 | } 52 | 53 | dependencies { 54 | implementation fileTree(include: ['*.jar'], dir: 'libs') 55 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 56 | implementation 'androidx.appcompat:appcompat:1.0.2' 57 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 58 | implementation 'androidx.media:media:1.0.0' 59 | implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha3' 60 | testImplementation 'junit:junit:4.12' 61 | androidTestImplementation 'androidx.test:runner:1.1.1' 62 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 63 | implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' 64 | implementation 'androidx.room:room-runtime:2.1.0-alpha03' 65 | kapt 'androidx.room:room-compiler:2.1.0-alpha03' 66 | implementation 'androidx.recyclerview:recyclerview:1.0.0' 67 | implementation 'androidx.cardview:cardview:1.0.0' 68 | implementation 'com.google.android.material:material:1.1.0-alpha01' 69 | implementation 'com.google.firebase:firebase-core:16.0.6' 70 | implementation 'com.crashlytics.sdk.android:crashlytics:2.9.8' 71 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.1' 72 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1' 73 | } 74 | 75 | apply plugin: 'com.google.gms.google-services' 76 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {} 23 | -keepnames class kotlinx.coroutines.CoroutineExceptionHandler {} 24 | -keepclassmembernames class kotlinx.** { 25 | volatile ; 26 | } 27 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/haroldadmin/kshitijchauhan/resumade/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("com.haroldadmin.kshitijchauhan.resumade", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/dev/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "", 4 | "project_id": "" 5 | }, 6 | "client": [ 7 | { 8 | "client_info": { 9 | "mobilesdk_app_id": "1:123456789012:android:1234567890123456", 10 | "android_client_info": { 11 | "package_name": "com.haroldadmin.kshitijchauhan.resumade" 12 | } 13 | }, 14 | "oauth_client": [ 15 | { 16 | "client_id": "", 17 | "client_type": 3 18 | }, 19 | { 20 | "client_id": "", 21 | "client_type": 1, 22 | "android_info": { 23 | "package_name": "com.haroldadmin.kshitijchauhan.resumade", 24 | "certificate_hash": "" 25 | } 26 | } 27 | ], 28 | "api_key": [ 29 | { 30 | "current_key": "" 31 | } 32 | ], 33 | "services": { 34 | "analytics_service": { 35 | "status": 2, 36 | "analytics_property": { 37 | "tracking_id": "" 38 | } 39 | }, 40 | "appinvite_service": { 41 | "status": 1, 42 | "other_platform_oauth_client": [] 43 | }, 44 | "ads_service": { 45 | "status": 1 46 | } 47 | } 48 | } 49 | ], 50 | "configuration_version": "1" 51 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/adapter/DiffUtilCallback.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.adapter 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.ResumeEntity 5 | 6 | class DiffUtilCallback ( 7 | val oldList : List, 8 | val newList : List): DiffUtil.Callback() { 9 | 10 | override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 11 | return oldList[oldItemPosition].id == newList[newItemPosition].id 12 | } 13 | 14 | override fun getOldListSize(): Int = oldList.size 15 | 16 | override fun getNewListSize(): Int = newList.size 17 | 18 | override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 19 | return oldList[oldItemPosition] == newList[newItemPosition] 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/adapter/EducationAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.appcompat.app.AlertDialog 7 | import androidx.appcompat.view.ContextThemeWrapper 8 | import androidx.recyclerview.widget.DiffUtil 9 | import androidx.recyclerview.widget.RecyclerView 10 | import com.google.android.material.button.MaterialButton 11 | import com.google.android.material.textfield.TextInputEditText 12 | import com.google.android.material.textfield.TextInputLayout 13 | import com.haroldadmin.kshitijchauhan.resumade.R 14 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Education 15 | import com.haroldadmin.kshitijchauhan.resumade.utilities.showKeyboard 16 | 17 | class EducationAdapter(val onSaveButtonClick: (Education) -> Unit, 18 | val onDeleteButtonClick: (Education) -> Unit, 19 | val onEditButtonClick: (Education) -> Unit) : RecyclerView.Adapter() { 20 | 21 | private var educationList: List = emptyList() 22 | 23 | override fun onCreateViewHolder(parent: ViewGroup, position: Int): EducationViewHolder { 24 | return EducationViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.card_education, parent, false)) 25 | } 26 | 27 | override fun getItemCount(): Int = educationList.size 28 | 29 | override fun onBindViewHolder(holder: EducationViewHolder, position: Int) { 30 | val education = educationList[position] 31 | holder.apply { 32 | setItem(education) 33 | bindClick() 34 | } 35 | } 36 | 37 | inner class EducationViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 38 | 39 | private lateinit var mEducation: Education 40 | 41 | private val instituteNameWrapper: TextInputLayout = itemView.findViewById(R.id.educationInstituteNameWrapper) 42 | private val instituteName: TextInputEditText = itemView.findViewById(R.id.educationInstituteName) 43 | private val degreeWrapper: TextInputLayout = itemView.findViewById(R.id.educationDegreeWrapper) 44 | private val degree: TextInputEditText = itemView.findViewById(R.id.educationDegree) 45 | private val performanceWrapper: TextInputLayout = itemView.findViewById(R.id.educationPerformanceWrapper) 46 | private val performance: TextInputEditText = itemView.findViewById(R.id.educationPerformance) 47 | private val yearWrapper: TextInputLayout = itemView.findViewById(R.id.educationYearWrapper) 48 | private val yearOfGraduation: TextInputEditText = itemView.findViewById(R.id.educationYear) 49 | private val saveButton: MaterialButton = itemView.findViewById(R.id.educationSaveButton) 50 | private val deleteButton: MaterialButton = itemView.findViewById(R.id.educationDeleteButton) 51 | 52 | fun setItem(education: Education) { 53 | mEducation = education 54 | this.apply { 55 | instituteName.setText(mEducation.instituteName) 56 | degree.setText(mEducation.degree) 57 | performance.setText(mEducation.performance) 58 | yearOfGraduation.setText(mEducation.year) 59 | saveButton.apply { 60 | this.text = if (mEducation.saved) { 61 | this.context.getString(R.string.editButtonText) 62 | } else { 63 | this.context.getString(R.string.saveButtonText) 64 | } 65 | } 66 | instituteNameWrapper.isEnabled = !mEducation.saved 67 | degreeWrapper.isEnabled = !mEducation.saved 68 | performanceWrapper.isEnabled = !mEducation.saved 69 | yearWrapper.isEnabled = !mEducation.saved 70 | } 71 | } 72 | 73 | fun bindClick() { 74 | saveButton.apply { 75 | setOnClickListener { 76 | if (mEducation.saved) { 77 | // Edit Mode 78 | onEditButtonClick(mEducation) 79 | instituteNameWrapper.apply { 80 | isEnabled = true 81 | requestFocus() 82 | showKeyboard(itemView.context) 83 | } 84 | degreeWrapper.isEnabled = true 85 | performanceWrapper.isEnabled = true 86 | yearWrapper.isEnabled = true 87 | this.text = this.context.getString(R.string.saveButtonText) 88 | } else { 89 | // Save Mode 90 | val tempInstituteName = this@EducationViewHolder.instituteName.text?.toString() 91 | ?: "" 92 | val tempDegree = this@EducationViewHolder.degree.text?.toString() ?: "" 93 | val tempPerformance = this@EducationViewHolder.performance.text?.toString() ?: "" 94 | val tempYear = this@EducationViewHolder.yearOfGraduation.text?.toString() ?: "" 95 | 96 | var passed = true 97 | 98 | if (tempInstituteName.trim().isEmpty()) { 99 | instituteNameWrapper.error = "Please enter an institute name" 100 | passed = false 101 | } else { 102 | instituteNameWrapper.isErrorEnabled = false 103 | } 104 | if (tempDegree.trim().isEmpty()) { 105 | degreeWrapper.error = "Can't be empty" 106 | passed = false 107 | } else { 108 | degreeWrapper.isErrorEnabled = false 109 | } 110 | if (tempPerformance.trim().isEmpty()) { 111 | performanceWrapper.error = "Can't be empty" 112 | passed = false 113 | } else { 114 | performanceWrapper.isErrorEnabled = false 115 | } 116 | if (tempYear.trim().toLongOrNull() == null) { 117 | yearWrapper.error = "Year must contain numbers only" 118 | passed = false 119 | } else if (tempYear.trim().isEmpty()) { 120 | yearWrapper.error = "Please enter the graduation year" 121 | passed = false 122 | } else { 123 | yearWrapper.isErrorEnabled = false 124 | } 125 | 126 | if (passed) { 127 | // Save the new values into the member variable 128 | mEducation.instituteName = tempInstituteName.trim() 129 | mEducation.degree = tempDegree.trim() 130 | mEducation.performance = tempPerformance.trim() 131 | mEducation.year = tempYear.trim() 132 | onSaveButtonClick(mEducation) 133 | 134 | text = this.context.getString(R.string.editButtonText) 135 | 136 | // Disable text fields 137 | instituteNameWrapper.isEnabled = false 138 | degreeWrapper.isEnabled = false 139 | performanceWrapper.isEnabled = false 140 | yearWrapper.isEnabled = false 141 | } 142 | } 143 | 144 | } 145 | } 146 | deleteButton.setOnClickListener { 147 | AlertDialog.Builder(ContextThemeWrapper(itemView.context, R.style.MyAlertDialog)) 148 | .setMessage("Are you sure you want to delete this education card?") 149 | .setPositiveButton("Yes") { _, _ -> 150 | onDeleteButtonClick(mEducation) 151 | } 152 | .setNegativeButton("No") { dialog, _ -> 153 | dialog.dismiss() 154 | } 155 | .create() 156 | .show() 157 | } 158 | } 159 | } 160 | 161 | fun updateEducationList(newEducationList: List) { 162 | val educationDiffUtilCallback = DiffUtilCallback(this.educationList, newEducationList) 163 | val diffResult = DiffUtil.calculateDiff(educationDiffUtilCallback) 164 | educationList = newEducationList 165 | diffResult.dispatchUpdatesTo(this) 166 | } 167 | 168 | } -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/adapter/ExperienceAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.appcompat.app.AlertDialog 7 | import androidx.appcompat.view.ContextThemeWrapper 8 | import androidx.recyclerview.widget.DiffUtil 9 | import androidx.recyclerview.widget.RecyclerView 10 | import com.google.android.material.button.MaterialButton 11 | import com.google.android.material.textfield.TextInputEditText 12 | import com.google.android.material.textfield.TextInputLayout 13 | import com.haroldadmin.kshitijchauhan.resumade.R 14 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Experience 15 | import com.haroldadmin.kshitijchauhan.resumade.utilities.showKeyboard 16 | 17 | class ExperienceAdapter(val onSaveButtonClick: (Experience) -> Unit, 18 | val onDeleteButtonClick: (Experience) -> Unit, 19 | val onEditButtonClick: (Experience) -> Unit) : RecyclerView.Adapter() { 20 | 21 | private var experienceList: List = emptyList() 22 | 23 | override fun onCreateViewHolder(parent: ViewGroup, position: Int): ExperienceViewHolder { 24 | return ExperienceViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.card_experience, parent, false)) 25 | } 26 | 27 | override fun getItemCount(): Int = experienceList.size 28 | 29 | override fun onBindViewHolder(holder: ExperienceViewHolder, position: Int) { 30 | val experience = experienceList[position] 31 | holder.apply { 32 | setItem(experience) 33 | bindClick() 34 | } 35 | } 36 | 37 | inner class ExperienceViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 38 | 39 | private lateinit var mExperience: Experience 40 | 41 | private val companyNameWrapper: TextInputLayout = itemView.findViewById(R.id.experienceCompanyNameWrapper) 42 | private val companyName: TextInputEditText = itemView.findViewById(R.id.experienceCompanyName) 43 | private val jobTitleWrapper: TextInputLayout = itemView.findViewById(R.id.experienceJobTitleWrapper) 44 | private val jobTitle: TextInputEditText = itemView.findViewById(R.id.experienceJobTitle) 45 | private val durationWrapper: TextInputLayout = itemView.findViewById(R.id.experienceDurationWrapper) 46 | private val duration: TextInputEditText = itemView.findViewById(R.id.experienceDuration) 47 | private val saveButton: MaterialButton = itemView.findViewById(R.id.experienceSaveButton) 48 | private val deleteButton: MaterialButton = itemView.findViewById(R.id.experienceDeleteButton) 49 | 50 | fun setItem(experience: Experience) { 51 | mExperience = experience 52 | this.apply { 53 | companyName.setText(mExperience.companyName) 54 | jobTitle.setText(mExperience.jobTitle) 55 | duration.setText(mExperience.duration) 56 | saveButton.apply { 57 | this.text = if (mExperience.saved) { 58 | this.context.getString(R.string.editButtonText) 59 | } else { 60 | this.context.getString(R.string.saveButtonText) 61 | } 62 | } 63 | companyNameWrapper.isEnabled = !mExperience.saved 64 | jobTitleWrapper.isEnabled = !mExperience.saved 65 | durationWrapper.isEnabled = !mExperience.saved 66 | } 67 | } 68 | 69 | fun bindClick() { 70 | saveButton.apply { 71 | setOnClickListener { 72 | if (mExperience.saved) { 73 | // Edit Mode 74 | onEditButtonClick(mExperience) 75 | 76 | // Enable text fields 77 | companyNameWrapper.apply { 78 | isEnabled = true 79 | requestFocus() 80 | showKeyboard(itemView.context) 81 | } 82 | jobTitleWrapper.isEnabled = true 83 | durationWrapper.isEnabled = true 84 | this.text = this.context.getString(R.string.saveButtonText) 85 | } else { 86 | // Save Mode 87 | 88 | // Save text from text fields and run checks 89 | val tempCompanyName = this@ExperienceViewHolder.companyName.text?.toString() 90 | ?: "" 91 | val tempDuration = this@ExperienceViewHolder.duration.text?.toString() ?: "" 92 | val tempJobTitle = this@ExperienceViewHolder.jobTitle.text?.toString() ?: "" 93 | 94 | var passed = true 95 | if (tempCompanyName.trim().isEmpty()) { 96 | companyNameWrapper.error = "Please enter the company name" 97 | passed = false 98 | } else { 99 | companyNameWrapper.isErrorEnabled = false 100 | } 101 | if (tempJobTitle.trim().isEmpty()) { 102 | jobTitleWrapper.error = "Please enter your job title" 103 | passed = false 104 | } else { 105 | jobTitleWrapper.isErrorEnabled = false 106 | } 107 | if (tempDuration.trim().isEmpty()) { 108 | durationWrapper.error = "Please enter the duration of your job" 109 | passed = false 110 | } else { 111 | durationWrapper.isErrorEnabled = false 112 | } 113 | 114 | if (passed) { 115 | // Save the new values into the member variable 116 | mExperience.companyName = tempCompanyName.trim() 117 | mExperience.duration = tempDuration.trim() 118 | mExperience.jobTitle = tempJobTitle.trim() 119 | onSaveButtonClick(mExperience) 120 | 121 | this.text = this.context.getString(R.string.editButtonText) 122 | 123 | // Disable text fields 124 | companyNameWrapper.isEnabled = false 125 | jobTitleWrapper.isEnabled = false 126 | durationWrapper.isEnabled = false 127 | } 128 | } 129 | 130 | } 131 | } 132 | deleteButton.setOnClickListener { 133 | AlertDialog.Builder(ContextThemeWrapper(itemView.context, R.style.MyAlertDialog)) 134 | .setMessage("Are you sure you want to delete this experience card?") 135 | .setPositiveButton("Yes") { _, _ -> 136 | onDeleteButtonClick(mExperience) 137 | } 138 | .setNegativeButton("No") { dialog, _ -> 139 | dialog.dismiss() 140 | } 141 | .create() 142 | .show() 143 | } 144 | } 145 | } 146 | 147 | fun updateExperienceList(newExperienceList: List) { 148 | val experienceDiffUtilCallback = DiffUtilCallback(this.experienceList, newExperienceList) 149 | val diffResult = DiffUtil.calculateDiff(experienceDiffUtilCallback) 150 | this.experienceList = newExperienceList 151 | diffResult.dispatchUpdatesTo(this) 152 | } 153 | } -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/adapter/FragmentAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.adapter 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.fragment.app.FragmentManager 5 | import androidx.fragment.app.FragmentPagerAdapter 6 | import com.haroldadmin.kshitijchauhan.resumade.ui.fragments.* 7 | 8 | class FragmentAdapter(manager : androidx.fragment.app.FragmentManager) : androidx.fragment.app.FragmentPagerAdapter(manager) { 9 | 10 | private val listOfFragments: List = listOf(PersonalFragment(), EducationFragment(), ExperienceFragment(), ProjectsFragment()) 11 | private val listOfTitles: List = listOf("Personal", "Education", "Experience", "Projects") 12 | 13 | override fun getItem(position: Int): androidx.fragment.app.Fragment = listOfFragments[position] 14 | 15 | override fun getCount(): Int = listOfFragments.size 16 | 17 | override fun getPageTitle(position: Int) = listOfTitles[position] 18 | 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/adapter/ProjectAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.appcompat.app.AlertDialog 7 | import androidx.appcompat.view.ContextThemeWrapper 8 | import androidx.recyclerview.widget.DiffUtil 9 | import androidx.recyclerview.widget.RecyclerView 10 | import com.google.android.material.button.MaterialButton 11 | import com.google.android.material.textfield.TextInputEditText 12 | import com.google.android.material.textfield.TextInputLayout 13 | import com.haroldadmin.kshitijchauhan.resumade.R 14 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Project 15 | import com.haroldadmin.kshitijchauhan.resumade.utilities.showKeyboard 16 | 17 | class ProjectAdapter(val onSaveButtonClick: (Project) -> Unit, 18 | val onDeleteButtonClick: (Project) -> Unit, 19 | val onEditButtonClick: (Project) -> Unit) : RecyclerView.Adapter() { 20 | 21 | private var projectList: List = emptyList() 22 | 23 | override fun onCreateViewHolder(parent: ViewGroup, position: Int): ProjectViewHolder { 24 | return ProjectViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.card_project, parent, false)) 25 | } 26 | 27 | override fun getItemCount(): Int = projectList.size 28 | 29 | override fun onBindViewHolder(holder: ProjectViewHolder, position: Int) { 30 | val project = projectList[position] 31 | holder.apply { 32 | setItem(project) 33 | bindClick() 34 | } 35 | } 36 | 37 | inner class ProjectViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 38 | 39 | private lateinit var mProject: Project 40 | 41 | private val projectNameWrapper: TextInputLayout = itemView.findViewById(R.id.projectNameWrapper) 42 | private val projectName: TextInputEditText = itemView.findViewById(R.id.projectName) 43 | private val projectRoleWrapper: TextInputLayout = itemView.findViewById(R.id.projectRoleWrapper) 44 | private val projectRole: TextInputEditText = itemView.findViewById(R.id.projectRole) 45 | private val projectLinkWrapper: TextInputLayout = itemView.findViewById(R.id.projectLinkWrapper) 46 | private val projectLink: TextInputEditText = itemView.findViewById(R.id.projectLink) 47 | private val projectDescriptionWrapper: TextInputLayout = itemView.findViewById(R.id.projectDescriptionWrapper) 48 | private val projectDescription: TextInputEditText = itemView.findViewById(R.id.projectDescription) 49 | private val saveButton: MaterialButton = itemView.findViewById(R.id.projectSaveButton) 50 | private val deleteButton: MaterialButton = itemView.findViewById(R.id.projectDeleteButton) 51 | 52 | fun setItem(project: Project) { 53 | mProject = project 54 | this.apply { 55 | projectName.setText(mProject.projectName) 56 | projectRole.setText(mProject.role) 57 | projectLink.setText(mProject.link) 58 | projectDescription.setText(mProject.description) 59 | saveButton.apply { 60 | text = if (mProject.saved) { 61 | this.context.getString(R.string.editButtonText) 62 | } else { 63 | this.context.getString(R.string.saveButtonText) 64 | } 65 | } 66 | projectNameWrapper.isEnabled = !mProject.saved 67 | projectRoleWrapper.isEnabled = !mProject.saved 68 | projectLinkWrapper.isEnabled = !mProject.saved 69 | projectDescriptionWrapper.isEnabled = !mProject.saved 70 | } 71 | } 72 | 73 | fun bindClick() { 74 | saveButton.apply { 75 | setOnClickListener { 76 | if (mProject.saved) { 77 | // Edit Mode 78 | 79 | onEditButtonClick(mProject) 80 | 81 | // Enable text fields 82 | projectNameWrapper.apply { 83 | isEnabled = true 84 | requestFocus() 85 | showKeyboard(itemView.context) 86 | } 87 | projectRoleWrapper.isEnabled = true 88 | projectLinkWrapper.isEnabled = true 89 | projectDescriptionWrapper.isEnabled = true 90 | this.text = this.context.getString(R.string.saveButtonText) 91 | } else { 92 | // Save Mode 93 | val tempProjectName = this@ProjectViewHolder.projectName.text?.toString() 94 | ?: "" 95 | val tempProjectRole = this@ProjectViewHolder.projectRole.text?.toString() 96 | ?: "" 97 | val tempProjectLink = this@ProjectViewHolder.projectLink.text?.toString() 98 | ?: "" 99 | val tempProjectDescription = this@ProjectViewHolder.projectDescription.text?.toString() 100 | ?: "" 101 | 102 | var passed = true 103 | 104 | if (tempProjectName.trim().isEmpty()) { 105 | projectNameWrapper.error = "Please enter the project name" 106 | passed = false 107 | } else { 108 | projectNameWrapper.isErrorEnabled = false 109 | } 110 | if (tempProjectRole.trim().isEmpty()) { 111 | projectRoleWrapper.error = "Please enter your role in the project" 112 | passed = false 113 | } else { 114 | projectRoleWrapper.isErrorEnabled = false 115 | } 116 | if (tempProjectDescription.trim().isEmpty()) { 117 | projectDescriptionWrapper.error = "Please provide a short description of the project" 118 | passed = false 119 | } else { 120 | projectDescriptionWrapper.isErrorEnabled = false 121 | } 122 | 123 | if (passed) { 124 | // Save the new values into the member variable 125 | mProject.projectName = tempProjectName 126 | mProject.role = tempProjectRole 127 | mProject.link = tempProjectLink 128 | mProject.description = tempProjectDescription 129 | onSaveButtonClick(mProject) 130 | 131 | this.text = this.context.getString(R.string.editButtonText) 132 | 133 | // Disable text fields 134 | projectNameWrapper.isEnabled = false 135 | projectRoleWrapper.isEnabled = false 136 | projectLinkWrapper.isEnabled = false 137 | projectDescriptionWrapper.isEnabled = false 138 | } 139 | } 140 | } 141 | } 142 | deleteButton.setOnClickListener { 143 | AlertDialog.Builder(ContextThemeWrapper(itemView.context, R.style.MyAlertDialog)) 144 | .setMessage("Are you sure you want to delete this project card?") 145 | .setPositiveButton("Yes") { _, _ -> 146 | onDeleteButtonClick(mProject) 147 | } 148 | .setNegativeButton("No") { dialog, _ -> 149 | dialog.dismiss() 150 | } 151 | .create() 152 | .show() 153 | } 154 | } 155 | } 156 | 157 | fun updateProjectList(newProjectList: List) { 158 | val projectDiffUtilCallback = DiffUtilCallback(this.projectList, newProjectList) 159 | val diffResult = DiffUtil.calculateDiff(projectDiffUtilCallback) 160 | this.projectList = newProjectList 161 | diffResult.dispatchUpdatesTo(this) 162 | } 163 | } -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/adapter/ResumeAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.adapter 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import android.widget.TextView 8 | import androidx.recyclerview.widget.RecyclerView 9 | import com.haroldadmin.kshitijchauhan.resumade.R 10 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Resume 11 | 12 | class ResumeAdapter(val onResumeCardClick: (resumeId: Long) -> Unit) : RecyclerView.Adapter() { 13 | 14 | private var resumesList : List = emptyList() 15 | 16 | override fun onCreateViewHolder(parent : ViewGroup, position : Int): ResumeViewHolder = ResumeViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.card_resume, parent, false)) 17 | 18 | override fun getItemCount(): Int = resumesList.size 19 | 20 | override fun onBindViewHolder(holder : ResumeViewHolder, position : Int) { 21 | val resume = resumesList[position] 22 | holder.apply { 23 | resumeNameTextView.text = resume.resumeName 24 | personNameTextView.text = resume.name 25 | personPhoneTextView.text = resume.phone 26 | personEmailTextView.text = resume.email 27 | bindClickListener() 28 | } 29 | } 30 | 31 | inner class ResumeViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView) { 32 | val resumeCard : View = itemView.findViewById(R.id.resumeItemView) 33 | val resumeNameTextView : TextView = itemView.findViewById(R.id.resumeNameTextView) 34 | val personNameTextView : TextView = itemView.findViewById(R.id.personalNameTextView) 35 | val personPhoneTextView : TextView = itemView.findViewById(R.id.personalPhoneTextView) 36 | val personEmailTextView : TextView = itemView.findViewById(R.id.personalEmailTextView) 37 | 38 | fun bindClickListener() { 39 | val resumeId = resumesList[adapterPosition].id 40 | resumeCard.setOnClickListener { 41 | onResumeCardClick(resumeId) 42 | } 43 | } 44 | } 45 | 46 | fun getResumeAtPosition(position: Int) = resumesList[position] 47 | 48 | fun updateResumesList(newResumesList : List) { 49 | val resumeDiffUtilCallback = DiffUtilCallback(this.resumesList, newResumesList) 50 | val diffResult = DiffUtil.calculateDiff(resumeDiffUtilCallback) 51 | this.resumesList = newResumesList 52 | diffResult.dispatchUpdatesTo(this) 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/adapter/SwipeToDeleteCallback.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.adapter 2 | 3 | import android.content.Context 4 | import android.graphics.* 5 | import android.graphics.drawable.ColorDrawable 6 | import androidx.core.content.ContextCompat 7 | import androidx.recyclerview.widget.RecyclerView 8 | import androidx.recyclerview.widget.ItemTouchHelper 9 | import com.haroldadmin.kshitijchauhan.resumade.R 10 | 11 | abstract class SwipeToDeleteCallback(context: Context) : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) { 12 | 13 | private val deleteIcon = ContextCompat.getDrawable(context, R.drawable.ic_round_delete_24px) 14 | private val printIcon = ContextCompat.getDrawable(context, R.drawable.ic_round_print_24px) 15 | private val background = ColorDrawable() 16 | private val backgroundColorDelete = ContextCompat.getColor(context, R.color.errorColor) 17 | private val backgroundColorPrint = ContextCompat.getColor(context, R.color.primaryDarkColor) 18 | private val clearPaint = Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) } 19 | 20 | override fun onMove(recyclerView: androidx.recyclerview.widget.RecyclerView, viewHolder: androidx.recyclerview.widget.RecyclerView.ViewHolder, target: androidx.recyclerview.widget.RecyclerView.ViewHolder): Boolean { 21 | return false 22 | } 23 | 24 | override fun onChildDraw(c: Canvas, recyclerView: androidx.recyclerview.widget.RecyclerView, viewHolder: androidx.recyclerview.widget.RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) { 25 | 26 | val itemView = viewHolder.itemView 27 | val itemHeight = itemView.bottom - itemView.top 28 | val isCanceled = dX == 0f && !isCurrentlyActive 29 | 30 | if (isCanceled) { 31 | clearCanvas(c, itemView.right + dX, itemView.top.toFloat(), itemView.right.toFloat(), itemView.bottom.toFloat()) 32 | super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) 33 | return 34 | } 35 | 36 | 37 | if (dX < 0) { 38 | val intrinsicWidth = deleteIcon?.intrinsicWidth ?: 0 39 | val intrinsicHeight = deleteIcon?.intrinsicHeight ?: 0 40 | 41 | // Draw the red delete background 42 | background.color = backgroundColorDelete 43 | background.setBounds(itemView.right + dX.toInt(), itemView.top, itemView.right, itemView.bottom) 44 | background.draw(c) 45 | 46 | // Calculate position of delete icon 47 | val deleteIconTop = itemView.top + (itemHeight - intrinsicHeight) / 2 48 | val deleteIconMargin = (itemHeight - intrinsicHeight) / 2 49 | val deleteIconLeft = itemView.right - deleteIconMargin - intrinsicWidth 50 | val deleteIconRight = itemView.right - deleteIconMargin 51 | val deleteIconBottom = deleteIconTop + intrinsicHeight 52 | 53 | // Draw the delete icon 54 | deleteIcon?.setBounds(deleteIconLeft, deleteIconTop, deleteIconRight, deleteIconBottom) 55 | deleteIcon?.draw(c) 56 | } else { 57 | val intrinsicWidth = printIcon?.intrinsicWidth ?: 0 58 | val intrinsicHeight = printIcon?.intrinsicHeight ?: 0 59 | 60 | background.color = backgroundColorPrint 61 | background.setBounds(itemView.left, itemView.top, itemView.left + dX.toInt(), itemView.bottom) 62 | background.draw(c) 63 | 64 | val printIconTop = itemView.top + (itemHeight - intrinsicHeight) / 2 65 | val printIconMargin = (itemHeight - intrinsicHeight) / 2 66 | val printIconLeft = itemView.left + printIconMargin 67 | val printIconRight = itemView.left + printIconMargin + intrinsicWidth 68 | val printIconBottom = printIconTop + intrinsicHeight 69 | 70 | printIcon?.setBounds(printIconLeft, printIconTop, printIconRight, printIconBottom) 71 | printIcon?.draw(c) 72 | } 73 | super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) 74 | } 75 | 76 | private fun clearCanvas(c: Canvas?, left: Float, top: Float, right: Float, bottom: Float) { 77 | c?.drawRect(left, top, right, bottom, clearPaint) 78 | } 79 | } -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/repository/LocalRepository.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.repository 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.LiveData 5 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.* 6 | import com.haroldadmin.kshitijchauhan.resumade.utilities.AppDispatchers 7 | import kotlinx.coroutines.withContext 8 | 9 | 10 | /* 11 | This is the local repository layer that 12 | interacts with the database. 13 | */ 14 | class LocalRepository(application: Application) : Repository { 15 | 16 | val database: ResumeDatabase = ResumeDatabase.getInstance(application) 17 | 18 | override fun getAllResume(): LiveData> = database.resumeDAO().getAllResume() 19 | 20 | override fun getResumeForId(resumeId: Long): LiveData = database.resumeDAO().getResumeForId(resumeId) 21 | 22 | override fun getSingleResumeForId(resumeId: Long) = database.resumeDAO().getSingleResume(resumeId) 23 | 24 | override suspend fun insertResume(resume: Resume): Long = withContext(AppDispatchers.diskDispatcher) { 25 | database.resumeDAO().insertResume(resume) 26 | } 27 | 28 | override suspend fun deleteResume(resume: Resume) = withContext(AppDispatchers.diskDispatcher) { 29 | database.resumeDAO().deleteResume(resume) 30 | } 31 | 32 | override suspend fun deleteResumeForId(resumeId: Long) = withContext(AppDispatchers.diskDispatcher) { 33 | database.resumeDAO().deleteResumeForId(resumeId) 34 | } 35 | 36 | override suspend fun updateResume(resume: Resume) = withContext(AppDispatchers.diskDispatcher) { 37 | database.resumeDAO().updateResume(resume) 38 | } 39 | 40 | override fun getLastResumeId(): LiveData = database.resumeDAO().getLastResumeId() 41 | 42 | override fun getAllEducationForResume(resumeId: Long): LiveData> = database.educationDAO().getEducationForResume(resumeId) 43 | 44 | override fun getAllEducationForResumeOnce(resumeId: Long): List = database.educationDAO().getEducationForResumeOnce(resumeId) 45 | 46 | override suspend fun insertEducation(education: Education): Long = 47 | withContext(AppDispatchers.diskDispatcher) { 48 | database.educationDAO().insertEducation(education) 49 | } 50 | 51 | override suspend fun deleteEducation(education: Education) = withContext(AppDispatchers.diskDispatcher) { 52 | database.educationDAO().deleteEducation(education) 53 | } 54 | 55 | override suspend fun updateEducation(education: Education) = withContext(AppDispatchers.diskDispatcher) { 56 | database.educationDAO().updateEducation(education) 57 | } 58 | 59 | override fun getAllExperienceForResume(resumeId: Long): LiveData> = database.experienceDAO().getExperienceForResume(resumeId) 60 | 61 | override fun getAllExperienceForResumeOnce(resumeId: Long): List = database.experienceDAO().getExperienceForResumeOnce(resumeId) 62 | 63 | override suspend fun insertExperience(experience: Experience): Long = 64 | withContext(AppDispatchers.diskDispatcher) { 65 | database.experienceDAO().insertExperience(experience) 66 | } 67 | 68 | override suspend fun deleteExperience(experience: Experience) = withContext(AppDispatchers.diskDispatcher) { 69 | database.experienceDAO().deleteExperience(experience) 70 | } 71 | 72 | override suspend fun updateExperience(experience: Experience) = withContext(AppDispatchers.diskDispatcher) { 73 | database.experienceDAO().updateExperience(experience) 74 | } 75 | 76 | override fun getAllProjectsForResume(resumeId: Long): LiveData> = database.projectsDAO().getProjectsForResume(resumeId) 77 | 78 | override fun getAllProjectsForResumeOnce(resumeId: Long): List = database.projectsDAO().getProjectsForResumeOnce(resumeId) 79 | 80 | override suspend fun insertProject(project: Project): Long = withContext(AppDispatchers.diskDispatcher) { 81 | database.projectsDAO().insertProject(project) 82 | } 83 | 84 | override suspend fun deleteProject(project: Project) = withContext(AppDispatchers.diskDispatcher) { 85 | database.projectsDAO().deleteProject(project) 86 | } 87 | 88 | override suspend fun updateProject(project: Project) = withContext(AppDispatchers.diskDispatcher) { 89 | database.projectsDAO().updateProject(project) 90 | } 91 | } -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/repository/Repository.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.repository 2 | 3 | import androidx.lifecycle.LiveData 4 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Education 5 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Experience 6 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Project 7 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Resume 8 | 9 | interface Repository { 10 | 11 | /* 12 | Any data repository should implement 13 | following methods. 14 | */ 15 | 16 | // Tasks related to resume-list 17 | fun getAllResume() : LiveData> 18 | fun getResumeForId(resumeId: Long): LiveData 19 | fun getSingleResumeForId(resumeId : Long) : Resume 20 | fun getLastResumeId() : LiveData 21 | suspend fun insertResume(resume: Resume): Long 22 | suspend fun deleteResume(resume : Resume) 23 | suspend fun updateResume(resume : Resume) 24 | suspend fun deleteResumeForId(resumeId : Long) 25 | 26 | // Tasks related to education-list 27 | fun getAllEducationForResume(resumeId: Long) : LiveData> 28 | fun getAllEducationForResumeOnce(resumeId: Long) : List 29 | suspend fun insertEducation(education: Education): Long 30 | suspend fun deleteEducation(education: Education) 31 | suspend fun updateEducation(education: Education) 32 | 33 | // Tasks related to experience-list 34 | fun getAllExperienceForResume(resumeId: Long) : LiveData> 35 | fun getAllExperienceForResumeOnce(resumeId: Long) : List 36 | suspend fun insertExperience(experience: Experience): Long 37 | suspend fun deleteExperience(experience: Experience) 38 | suspend fun updateExperience(experience: Experience) 39 | 40 | // Tasks related to projects-list 41 | fun getAllProjectsForResume(resumeId: Long) : LiveData> 42 | fun getAllProjectsForResumeOnce(resumeId : Long) : List 43 | suspend fun insertProject(project: Project): Long 44 | suspend fun deleteProject(project: Project) 45 | suspend fun updateProject(project: Project) 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/repository/database/Daos.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.repository.database 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.room.* 5 | 6 | @Dao 7 | interface ResumeDAO { 8 | 9 | @Query("SELECT * FROM resumes") 10 | fun getAllResume(): LiveData> 11 | 12 | @Query("SELECT * FROM resumes WHERE id=:resumeId") 13 | fun getResumeForId(resumeId : Long) : LiveData 14 | 15 | @Query("SELECT MAX(id) FROM resumes") 16 | fun getLastResumeId() : LiveData 17 | 18 | @Insert 19 | fun insertResume(resume: Resume) : Long 20 | 21 | @Delete 22 | fun deleteResume(resume : Resume) 23 | 24 | @Query("DELETE FROM resumes WHERE id=:resumeId") 25 | fun deleteResumeForId(resumeId : Long) 26 | 27 | @Update 28 | fun updateResume(resume : Resume) 29 | 30 | @Query("SELECT * FROM resumes WHERE id=:resumeId") 31 | fun getSingleResume(resumeId: Long) : Resume 32 | } 33 | 34 | @Dao 35 | interface EducationDAO { 36 | 37 | @Query("SELECT * FROM education") 38 | fun getAllEducation() : MutableList 39 | 40 | @Query("SELECT * FROM education WHERE resumeId=:resumeId") 41 | fun getEducationForResume(resumeId : Long) : LiveData> 42 | 43 | @Query("SELECT * FROM education WHERE resumeId=:resumeId") 44 | fun getEducationForResumeOnce(resumeId : Long) : List 45 | 46 | @Query("SELECT count(*) FROM education") 47 | fun getEducationId() : Long 48 | 49 | @Insert 50 | fun insertEducation(education : Education) : Long 51 | 52 | @Delete 53 | fun deleteEducation(education : Education) 54 | 55 | @Update 56 | fun updateEducation(education: Education) 57 | } 58 | 59 | @Dao 60 | interface ExperienceDAO { 61 | 62 | @Query("SELECT * FROM experience") 63 | fun getAllExperience() : LiveData> 64 | 65 | @Query("SELECT * FROM experience WHERE resumeId=:resumeId") 66 | fun getExperienceForResume(resumeId : Long) : LiveData> 67 | 68 | @Query("SELECT * FROM experience WHERE resumeId=:resumeId") 69 | fun getExperienceForResumeOnce(resumeId : Long) : List 70 | 71 | @Query("SELECT count(*) FROM experience") 72 | fun getExperienceId() : Long 73 | 74 | @Insert 75 | fun insertExperience(experience : Experience) : Long 76 | 77 | @Delete 78 | fun deleteExperience(experience : Experience) 79 | 80 | @Update 81 | fun updateExperience(experience : Experience) 82 | } 83 | 84 | @Dao 85 | interface ProjectsDAO { 86 | 87 | @Query("SELECT * FROM projects") 88 | fun getAllProjects() : LiveData> 89 | 90 | @Query("SELECT * FROM projects WHERE resumeId=:resumeId") 91 | fun getProjectsForResume(resumeId: Long) : LiveData> 92 | 93 | @Query("SELECT * FROM projects WHERE resumeId=:resumeId") 94 | fun getProjectsForResumeOnce(resumeId: Long) : List 95 | 96 | @Query("SELECT count(*) FROM projects") 97 | fun getProjectId() : Long 98 | 99 | @Insert 100 | fun insertProject(project : Project) : Long 101 | 102 | @Update 103 | fun updateProject(project : Project) 104 | 105 | @Delete 106 | fun deleteProject(project : Project) 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/repository/database/Entities.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.repository.database 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.ForeignKey 6 | import androidx.room.PrimaryKey 7 | 8 | abstract class ResumeEntity { 9 | @PrimaryKey(autoGenerate = true) 10 | var id : Long = 0L 11 | 12 | @ColumnInfo(name = "saved") 13 | var saved : Boolean = false 14 | 15 | abstract fun isEmpty() : Boolean 16 | 17 | fun isNotEmpty() : Boolean = !isEmpty() 18 | } 19 | 20 | @Entity(tableName = "resumes") 21 | data class Resume(@ColumnInfo(name = "resumeName") var resumeName: String = "My Resume", 22 | @ColumnInfo(name = "name") var name: String, 23 | @ColumnInfo(name = "phone") var phone: String, 24 | @ColumnInfo(name = "email") var email: String, 25 | @ColumnInfo(name = "currentCity") var currentCity: String, 26 | @ColumnInfo(name = "description") var description: String, 27 | @ColumnInfo(name = "skills") var skills: String, 28 | @ColumnInfo(name = "hobbies") var hobbies: String) : ResumeEntity() { 29 | 30 | companion object { 31 | fun emptyResume() = Resume("", "", "", "", "", "", "", "") 32 | } 33 | 34 | override fun isEmpty() : Boolean { 35 | if (name.isBlank() && 36 | phone.isBlank() && 37 | email.isBlank() && 38 | currentCity.isBlank() && 39 | description.isBlank() && 40 | skills.isBlank() && 41 | hobbies.isBlank()) { return true } 42 | return false 43 | } 44 | } 45 | 46 | @Entity(tableName = "education", 47 | foreignKeys = [(ForeignKey(entity = Resume::class, 48 | parentColumns = arrayOf("id"), 49 | childColumns = arrayOf("resumeId"), 50 | onDelete = ForeignKey.CASCADE))]) 51 | data class Education ( 52 | @ColumnInfo(name = "instituteName") 53 | var instituteName: String, 54 | @ColumnInfo(name = "degree") 55 | var degree: String, 56 | @ColumnInfo(name = "yearOfGraduation") 57 | var year: String, 58 | @ColumnInfo(name = "performance") 59 | var performance: String, 60 | @ColumnInfo(name = "resumeId") 61 | var resumeId: Long) : ResumeEntity() { 62 | 63 | override fun isEmpty() : Boolean { 64 | if (instituteName.isBlank() && 65 | degree.isBlank() && 66 | year.isBlank() && 67 | performance.isBlank()) { 68 | return true 69 | } 70 | return false 71 | } 72 | 73 | } 74 | 75 | @Entity(tableName = "experience", 76 | foreignKeys = [(ForeignKey(entity = Resume::class, 77 | parentColumns = arrayOf("id"), 78 | childColumns = arrayOf("resumeId"), 79 | onDelete = ForeignKey.CASCADE))]) 80 | data class Experience ( 81 | 82 | @ColumnInfo(name = "companyName") 83 | var companyName: String, 84 | @ColumnInfo(name = "jobTitle") 85 | var jobTitle: String, 86 | @ColumnInfo(name = "duration") 87 | var duration: String, 88 | @ColumnInfo(name = "resumeId") 89 | var resumeId: Long) : ResumeEntity() { 90 | 91 | override fun isEmpty(): Boolean { 92 | if (companyName.isBlank() && 93 | jobTitle.isBlank() && 94 | duration.isBlank()) { 95 | return true 96 | } 97 | return false 98 | } 99 | } 100 | 101 | @Entity(tableName = "projects", 102 | foreignKeys = [(ForeignKey(entity = Resume::class, 103 | parentColumns = arrayOf("id"), 104 | childColumns = arrayOf("resumeId"), 105 | onDelete = ForeignKey.CASCADE))]) 106 | data class Project ( 107 | 108 | @ColumnInfo(name = "projectName") 109 | var projectName: String, 110 | @ColumnInfo(name = "role") 111 | var role: String, 112 | @ColumnInfo(name = "link") 113 | var link: String, 114 | @ColumnInfo(name = "description") 115 | var description: String, 116 | @ColumnInfo(name = "resumeId") 117 | var resumeId: Long) : ResumeEntity() { 118 | 119 | override fun isEmpty(): Boolean { 120 | if (projectName.isBlank() && 121 | role.isBlank() && 122 | link.isBlank() && 123 | description.isBlank()) { 124 | return true 125 | } 126 | return false 127 | } 128 | } -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/repository/database/ResumeDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.repository.database 2 | 3 | import android.content.Context 4 | import androidx.room.Database 5 | import androidx.room.Room 6 | import androidx.room.RoomDatabase 7 | import androidx.sqlite.db.SupportSQLiteDatabase 8 | import com.haroldadmin.kshitijchauhan.resumade.utilities.AppDispatchers 9 | import kotlinx.coroutines.GlobalScope 10 | import kotlinx.coroutines.launch 11 | 12 | @Database(entities = [(Resume::class), (Education::class), (Experience::class), (Project::class)], version = 1) 13 | abstract class ResumeDatabase : RoomDatabase() { 14 | 15 | abstract fun resumeDAO() : ResumeDAO 16 | abstract fun educationDAO() : EducationDAO 17 | abstract fun experienceDAO() : ExperienceDAO 18 | abstract fun projectsDAO() : ProjectsDAO 19 | 20 | companion object : SingletonHolder( 21 | { 22 | Room.databaseBuilder(it.applicationContext, 23 | ResumeDatabase::class.java, 24 | "resumes") 25 | .addCallback(object : Callback() { 26 | override fun onCreate(db: SupportSQLiteDatabase) { 27 | super.onCreate(db) 28 | GlobalScope.launch (AppDispatchers.diskDispatcher) { 29 | val resume = Resume( 30 | "Example Resume", 31 | "John Doe", 32 | "1234567890", 33 | "johndoe@example.com", 34 | "San Francisco", 35 | "The most average guy on this planet. Will do any task you throw at me, and I'll do it in an average manner.", 36 | "Business Management, Average-anything", 37 | "Guitar, Biking, Cooking") 38 | resume.saved = true 39 | ResumeDatabase.getInstance(it).resumeDAO().insertResume(resume) 40 | } 41 | } 42 | }) 43 | .fallbackToDestructiveMigration() 44 | .build() 45 | } 46 | ) 47 | 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/repository/database/SingletonHolder.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.repository.database 2 | 3 | open class SingletonHolder(creator: (A) -> T) { 4 | private var creator: ((A) -> T)? = creator 5 | @Volatile private var instance: T? = null 6 | 7 | fun getInstance(arg: A): T { 8 | val i = instance 9 | if (i != null) { 10 | return i 11 | } 12 | 13 | return synchronized(this) { 14 | val i2 = instance 15 | if (i2 != null) { 16 | i2 17 | } else { 18 | val created = creator!!(arg) 19 | instance = created 20 | creator = null 21 | created 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/ui/activities/AboutUsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.ui.activities 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import androidx.appcompat.app.AppCompatActivity 6 | import android.os.Bundle 7 | import com.haroldadmin.kshitijchauhan.resumade.R 8 | import kotlinx.android.synthetic.main.activity_about_us.* 9 | import androidx.core.content.ContextCompat 10 | import android.view.WindowManager 11 | 12 | 13 | class AboutUsActivity : AppCompatActivity() { 14 | 15 | override fun onCreate(savedInstanceState: Bundle?) { 16 | super.onCreate(savedInstanceState) 17 | setContentView(R.layout.activity_about_us) 18 | 19 | val window = this.window 20 | 21 | window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) 22 | window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) 23 | window.statusBarColor = ContextCompat.getColor(this, R.color.secondaryColor) 24 | 25 | val text = resources.getString(R.string.aboutUsText) 26 | aboutLinksTextView.text = text 27 | appRepoRow.setOnClickListener { 28 | val url = "https://www.github.com/haroldadmin/resumade" 29 | val intent = Intent(Intent.ACTION_VIEW) 30 | intent.data = Uri.parse(url) 31 | startActivity(intent) 32 | } 33 | 34 | devProfileRow.setOnClickListener { 35 | val url = "https://www.github.com/haroldadmin" 36 | val intent = Intent(Intent.ACTION_VIEW) 37 | intent.data = Uri.parse(url) 38 | startActivity(intent) 39 | } 40 | 41 | standardResumeRow.setOnClickListener { 42 | val url = "http://www.standardresume.co" 43 | val intent = Intent(Intent.ACTION_VIEW) 44 | intent.data = Uri.parse(url) 45 | startActivity(intent) 46 | } 47 | 48 | rateAndReviewRow.setOnClickListener { 49 | val intent = Intent(Intent.ACTION_VIEW) 50 | intent.data = Uri.parse("market://details?id=com.haroldadmin.kshitijchauhan.resumade") 51 | startActivity(intent) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/ui/activities/CreateResumeActivity.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.ui.activities 2 | 3 | import android.app.ProgressDialog.show 4 | import android.content.Intent 5 | import android.graphics.drawable.Drawable 6 | import android.os.Bundle 7 | import android.view.Menu 8 | import android.view.MenuItem 9 | import android.webkit.WebView 10 | import androidx.appcompat.app.AlertDialog 11 | import androidx.appcompat.app.AppCompatActivity 12 | import androidx.appcompat.view.ContextThemeWrapper 13 | import androidx.core.content.ContextCompat 14 | import androidx.lifecycle.ViewModelProviders 15 | import androidx.viewpager.widget.ViewPager 16 | import com.google.android.material.floatingactionbutton.FloatingActionButton 17 | import com.google.android.material.snackbar.Snackbar 18 | import com.haroldadmin.kshitijchauhan.resumade.R 19 | import com.haroldadmin.kshitijchauhan.resumade.adapter.FragmentAdapter 20 | import com.haroldadmin.kshitijchauhan.resumade.ui.activities.MainActivity.Companion.EXTRA_RESUME_ID 21 | import com.haroldadmin.kshitijchauhan.resumade.utilities.AppDispatchers 22 | import com.haroldadmin.kshitijchauhan.resumade.utilities.buildHtml 23 | import com.haroldadmin.kshitijchauhan.resumade.utilities.createPrintJob 24 | import com.haroldadmin.kshitijchauhan.resumade.utilities.hideKeyboard 25 | import com.haroldadmin.kshitijchauhan.resumade.viewmodel.CreateResumeViewModel 26 | import kotlinx.android.synthetic.main.activity_create_resume.* 27 | import kotlinx.coroutines.* 28 | 29 | class CreateResumeActivity : AppCompatActivity(), CoroutineScope { 30 | 31 | private val createResumeActivityJob = Job() 32 | override val coroutineContext = Dispatchers.Main + createResumeActivityJob 33 | 34 | private val TAG: String = this::class.java.simpleName 35 | private val EXTRA_HTML: String = "html" 36 | private lateinit var createResumeViewModel: CreateResumeViewModel 37 | private lateinit var resumeFragmentAdapter: FragmentAdapter 38 | private lateinit var createResumeFab: FloatingActionButton 39 | private lateinit var viewPager: ViewPager 40 | private lateinit var webView: WebView 41 | private var addIcon: Drawable? = null 42 | private var doneIcon: Drawable? = null 43 | 44 | companion object { 45 | var currentResumeId: Long = -1L 46 | } 47 | 48 | override fun onCreate(savedInstanceState: Bundle?) { 49 | super.onCreate(savedInstanceState) 50 | setContentView(R.layout.activity_create_resume) 51 | 52 | setSupportActionBar(createResumeToolbar) 53 | supportActionBar?.title = resources.getString(R.string.createResumeTitle) 54 | 55 | createResumeFab = findViewById(R.id.createResumeFab) 56 | viewPager = findViewById(R.id.createResumeViewpager) 57 | addIcon = ContextCompat.getDrawable(this, R.drawable.ic_round_add_24px) 58 | doneIcon = ContextCompat.getDrawable(this, R.drawable.ic_round_done_24px) 59 | 60 | val intent = intent 61 | if (intent != null && intent.hasExtra(EXTRA_RESUME_ID)) { 62 | /* 63 | This means that we are currently working with 64 | an existing resume in the database 65 | */ 66 | currentResumeId = intent.getLongExtra(EXTRA_RESUME_ID, -1L) 67 | } 68 | 69 | createResumeViewModel = ViewModelProviders 70 | .of(this) 71 | .get(CreateResumeViewModel::class.java) 72 | 73 | resumeFragmentAdapter = FragmentAdapter(supportFragmentManager) 74 | viewPager.adapter = resumeFragmentAdapter 75 | viewPager.offscreenPageLimit = 4 76 | createResumeTabs.setupWithViewPager(createResumeViewpager) 77 | createResumeFab.hide() 78 | 79 | viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { 80 | override fun onPageScrollStateChanged(p0: Int) { 81 | // Do nothing 82 | } 83 | 84 | override fun onPageScrolled(p0: Int, p1: Float, p2: Int) { 85 | // Do nothing 86 | } 87 | 88 | override fun onPageSelected(position: Int) { 89 | adjustFabBehaviour(position) 90 | } 91 | }) 92 | 93 | } 94 | 95 | override fun onBackPressed() { 96 | /* 97 | We use the value of saved from the ViewModel 98 | because the value inside the activity is destroyed 99 | on every configuration change. It should only be used 100 | once: while initializing the viewmodel. 101 | */ 102 | if (!createResumeViewModel.personalDetailsSaved || !createResumeViewModel.educationDetailsSaved || !createResumeViewModel.experienceDetailsSaved || !createResumeViewModel.projectDetailsSaved) { 103 | AlertDialog.Builder(ContextThemeWrapper(this, R.style.MyAlertDialog)) 104 | .setTitle("Unsaved Details") 105 | .setMessage("Some details remain unsaved. Stay to view them.") 106 | .setPositiveButton("Stay") { _, _ -> 107 | checkIfDetailsSaved() 108 | } 109 | .setNegativeButton("Delete") { _, _ -> 110 | createResumeViewModel.deleteTempResume() 111 | super.onBackPressed() 112 | } 113 | .create() 114 | .show() 115 | } else { 116 | super.onBackPressed() 117 | } 118 | } 119 | 120 | override fun onCreateOptionsMenu(menu: Menu?): Boolean { 121 | menuInflater.inflate(R.menu.menu_create_resume_activity, menu) 122 | return true 123 | } 124 | 125 | override fun onOptionsItemSelected(item: MenuItem?): Boolean { 126 | return when (item?.itemId) { 127 | R.id.done -> run { 128 | this@CreateResumeActivity.hideKeyboard() 129 | if (checkIfDetailsSaved()) { 130 | finish() 131 | } 132 | return true 133 | } 134 | R.id.print -> run { 135 | this@CreateResumeActivity.hideKeyboard() 136 | if (checkIfDetailsSaved()) { 137 | launch(AppDispatchers.computationDispatcher) { 138 | val html = buildHtml(createResumeViewModel.resume.value!!, createResumeViewModel.educationList.value!!, createResumeViewModel.experienceList.value!!, createResumeViewModel.projectsList.value!!) 139 | withContext(AppDispatchers.mainThreadDispatcher) { 140 | webView = WebView(this@CreateResumeActivity) 141 | webView.loadDataWithBaseURL(null, html, "text/html", "UTF-8", null) 142 | webView.createPrintJob(this@CreateResumeActivity) 143 | } 144 | } 145 | } 146 | true 147 | } 148 | R.id.preview -> run { 149 | this@CreateResumeActivity.hideKeyboard() 150 | if (checkIfDetailsSaved()) { 151 | launch(AppDispatchers.computationDispatcher) { 152 | val html = buildHtml(createResumeViewModel.resume.value!!, createResumeViewModel.educationList.value!!, createResumeViewModel.experienceList.value!!, createResumeViewModel.projectsList.value!!) 153 | val intent = Intent(this@CreateResumeActivity, PreviewActivity::class.java) 154 | intent.putExtra(EXTRA_HTML, html) 155 | startActivity(intent) 156 | } 157 | } 158 | true 159 | } 160 | else -> super.onOptionsItemSelected(item) 161 | } 162 | } 163 | 164 | fun adjustFabBehaviour(position: Int) { 165 | when (position) { 166 | 0 -> fabBehaviourPersonalFragment() 167 | 1 -> fabBehaviourEducationFragment() 168 | 2 -> fabBehaviourExperienceFragment() 169 | 3 -> fabBehaviourProjectFragment() 170 | } 171 | } 172 | 173 | private fun fabBehaviourPersonalFragment() { 174 | createResumeFab.hide() 175 | } 176 | 177 | private fun fabBehaviourEducationFragment() { 178 | createResumeFab.apply { 179 | hide() 180 | setImageDrawable(addIcon) 181 | show() 182 | setOnClickListener { 183 | createResumeViewModel.apply { 184 | insertBlankEducation() 185 | educationDetailsSaved = false 186 | } 187 | } 188 | } 189 | } 190 | 191 | private fun fabBehaviourExperienceFragment() { 192 | createResumeFab.apply { 193 | hide() 194 | setImageDrawable(addIcon) 195 | show() 196 | setOnClickListener { 197 | createResumeViewModel.apply { 198 | insertBlankExperience() 199 | experienceDetailsSaved = false 200 | } 201 | } 202 | } 203 | } 204 | 205 | private fun fabBehaviourProjectFragment() { 206 | createResumeFab.apply { 207 | hide() 208 | setImageDrawable(addIcon) 209 | show() 210 | setOnClickListener { 211 | createResumeViewModel.apply { 212 | insertBlankProject() 213 | projectDetailsSaved = false 214 | } 215 | } 216 | } 217 | } 218 | 219 | fun displaySnackbar(text: String) { 220 | Snackbar.make(rootCoordinatorLayout, text, Snackbar.LENGTH_SHORT).show() 221 | } 222 | 223 | private fun checkIfDetailsSaved(): Boolean { 224 | with(createResumeViewModel) { 225 | if (!personalDetailsSaved) { 226 | viewPager.setCurrentItem(0, true) 227 | Snackbar.make(rootCoordinatorLayout, "Personal details unsaved", Snackbar.LENGTH_SHORT).show() 228 | return false 229 | } 230 | if (!educationDetailsSaved) { 231 | viewPager.setCurrentItem(1, true) 232 | Snackbar.make(rootCoordinatorLayout, "Education details unsaved", Snackbar.LENGTH_SHORT).show() 233 | return false 234 | } 235 | if (!experienceDetailsSaved) { 236 | viewPager.setCurrentItem(2, true) 237 | Snackbar.make(rootCoordinatorLayout, "Experience details unsaved", Snackbar.LENGTH_SHORT).show() 238 | return false 239 | } 240 | if (!projectDetailsSaved) { 241 | viewPager.setCurrentItem(3, true) 242 | Snackbar.make(rootCoordinatorLayout, "Project details unsaved", Snackbar.LENGTH_SHORT).show() 243 | return false 244 | } 245 | return true 246 | } 247 | } 248 | } 249 | 250 | -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/ui/activities/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.ui.activities 2 | 3 | import android.content.Intent 4 | import android.graphics.Color 5 | import android.os.Bundle 6 | import android.text.SpannableString 7 | import android.text.style.ForegroundColorSpan 8 | import android.view.Menu 9 | import android.view.MenuItem 10 | import android.webkit.WebView 11 | import androidx.appcompat.app.AlertDialog 12 | import androidx.appcompat.app.AppCompatActivity 13 | import androidx.appcompat.view.ContextThemeWrapper 14 | import androidx.lifecycle.Observer 15 | import androidx.lifecycle.ViewModelProviders 16 | import androidx.recyclerview.widget.ItemTouchHelper 17 | import com.haroldadmin.kshitijchauhan.resumade.R 18 | import com.haroldadmin.kshitijchauhan.resumade.adapter.ResumeAdapter 19 | import com.haroldadmin.kshitijchauhan.resumade.adapter.SwipeToDeleteCallback 20 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Education 21 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Experience 22 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Project 23 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Resume 24 | import com.haroldadmin.kshitijchauhan.resumade.utilities.* 25 | import com.haroldadmin.kshitijchauhan.resumade.viewmodel.MainViewModel 26 | import kotlinx.android.synthetic.main.activity_main.* 27 | import kotlinx.coroutines.* 28 | 29 | class MainActivity : AppCompatActivity(), CoroutineScope { 30 | 31 | private val mainActivityJob = Job() 32 | override val coroutineContext = Dispatchers.Main + mainActivityJob 33 | 34 | private val TAG = this::class.java.simpleName 35 | private lateinit var mainViewModel: MainViewModel 36 | private lateinit var resumeAdapter: ResumeAdapter 37 | private lateinit var linearLayoutManager: androidx.recyclerview.widget.LinearLayoutManager 38 | private lateinit var resumesRecyclerView: androidx.recyclerview.widget.RecyclerView 39 | private lateinit var webView: WebView 40 | 41 | 42 | companion object { 43 | const val EXTRA_RESUME_ID: String = "resumeId" 44 | } 45 | 46 | override fun onCreate(savedInstanceState: Bundle?) { 47 | super.onCreate(savedInstanceState) 48 | setContentView(R.layout.activity_main) 49 | 50 | setSupportActionBar(mainActivityToolbar) 51 | collapsingToolbarLayout.title = resources.getString(R.string.app_name) 52 | 53 | mainViewModel = ViewModelProviders 54 | .of(this) 55 | .get(MainViewModel::class.java) 56 | 57 | resumesRecyclerView = findViewById(R.id.resumesListRecyclerView) 58 | webView = WebView(this) 59 | 60 | setupRecyclerView() 61 | 62 | mainViewModel.resumesList 63 | .observe(this, Observer { 64 | resumeAdapter.updateResumesList(it ?: emptyList()) 65 | toggleNoResumesLayout(it?.size ?: 0) 66 | }) 67 | 68 | addResumeFab.setOnClickListener { 69 | val newResumeId: Long = -1 70 | val intent = Intent(this, CreateResumeActivity::class.java) 71 | intent.putExtra(EXTRA_RESUME_ID, newResumeId) 72 | startActivity(intent) 73 | } 74 | } 75 | 76 | override fun onCreateOptionsMenu(menu: Menu?): Boolean { 77 | menuInflater.inflate(R.menu.menu_main_activity, menu) 78 | val span = SpannableString("About") 79 | span.setSpan(ForegroundColorSpan(Color.BLACK), 0, span.length, 0) 80 | menu?.getItem(0)?.title = span 81 | return true 82 | } 83 | 84 | override fun onOptionsItemSelected(item: MenuItem?): Boolean { 85 | return when (item?.itemId) { 86 | R.id.about -> { 87 | startActivity(Intent(this, AboutUsActivity::class.java)) 88 | true 89 | } 90 | else -> super.onOptionsItemSelected(item) 91 | } 92 | 93 | } 94 | 95 | private fun toggleNoResumesLayout(size: Int) { 96 | if (size > 0) { 97 | resumesListRecyclerView.visible() 98 | noResumesView.invisible() 99 | } else { 100 | resumesListRecyclerView.invisible() 101 | noResumesView.visible() 102 | mainActivityAppbarLayout.setExpanded(true, true) 103 | } 104 | } 105 | 106 | private fun setupRecyclerView() { 107 | resumeAdapter = ResumeAdapter { resumeId: Long -> 108 | val intent = Intent(this, CreateResumeActivity::class.java) 109 | intent.putExtra(EXTRA_RESUME_ID, resumeId) 110 | startActivity(intent) 111 | } 112 | linearLayoutManager = androidx.recyclerview.widget.LinearLayoutManager(this) 113 | val dividerItemDecoration = androidx.recyclerview.widget.DividerItemDecoration(resumesRecyclerView.context, linearLayoutManager.orientation) 114 | dividerItemDecoration.setDrawable(this.getDrawable(R.drawable.list_divider)) 115 | resumesRecyclerView.apply { 116 | adapter = resumeAdapter 117 | layoutManager = linearLayoutManager 118 | addItemDecoration(dividerItemDecoration) 119 | } 120 | val itemTouchHelper = ItemTouchHelper(object : SwipeToDeleteCallback(this) { 121 | override fun onSwiped(viewholder: androidx.recyclerview.widget.RecyclerView.ViewHolder, direction: Int) { 122 | val position = viewholder.adapterPosition 123 | val id: Long = resumeAdapter.getResumeAtPosition(position).id 124 | if (direction == ItemTouchHelper.LEFT) { 125 | AlertDialog.Builder(ContextThemeWrapper(this@MainActivity, R.style.MyAlertDialog)) 126 | .setMessage("Are you sure you want to delete this resume?") 127 | .setPositiveButton("Yes") { _, _ -> 128 | mainViewModel.deleteResume(resumeAdapter.getResumeAtPosition(position)) 129 | } 130 | .setNegativeButton("No") { dialog, _ -> 131 | resumeAdapter.notifyItemChanged(position) 132 | dialog.dismiss() 133 | } 134 | .create() 135 | .show() 136 | } else { 137 | launch { 138 | lateinit var resume: Resume 139 | lateinit var educationList: List 140 | lateinit var experienceList: List 141 | lateinit var projectList: List 142 | lateinit var html: String 143 | 144 | withContext(AppDispatchers.diskDispatcher) { 145 | resume = mainViewModel.getResumeForId(id) 146 | educationList = mainViewModel.getEducationForResume(id) 147 | experienceList = mainViewModel.getExperienceForResume(id) 148 | projectList = mainViewModel.getProjectForResume(id) 149 | } 150 | withContext(AppDispatchers.computationDispatcher) { 151 | html = buildHtml(resume, educationList, experienceList, projectList) 152 | } 153 | 154 | webView = WebView(this@MainActivity) 155 | webView.loadDataWithBaseURL(null, html, "text/html", "UTF-8", null) 156 | webView.createPrintJob(this@MainActivity) 157 | resumeAdapter.notifyItemChanged(position) 158 | } 159 | } 160 | } 161 | }) 162 | itemTouchHelper.attachToRecyclerView(resumesRecyclerView) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/ui/activities/PreviewActivity.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.ui.activities 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.appcompat.widget.Toolbar 6 | import android.webkit.WebView 7 | import com.haroldadmin.kshitijchauhan.resumade.R 8 | 9 | class PreviewActivity : AppCompatActivity() { 10 | 11 | private val EXTRA_HTML: String = "html" 12 | private lateinit var toolbar: Toolbar 13 | private lateinit var webView: WebView 14 | 15 | override fun onCreate(savedInstanceState: Bundle?) { 16 | super.onCreate(savedInstanceState) 17 | setContentView(R.layout.activity_preview) 18 | webView = findViewById(R.id.previewActivityWebView) 19 | toolbar = findViewById(R.id.previewActivityToolbar) 20 | 21 | setSupportActionBar(toolbar) 22 | supportActionBar?.title = "Preview" 23 | 24 | val receivedIntent = intent 25 | val html = receivedIntent.getStringExtra(EXTRA_HTML) 26 | 27 | webView.loadDataWithBaseURL(null, html, "text/html", "utf-8", null) 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/ui/fragments/EducationFragment.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.ui.fragments 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.lifecycle.Observer 9 | import androidx.lifecycle.ViewModelProviders 10 | import androidx.recyclerview.widget.LinearLayoutManager 11 | import androidx.recyclerview.widget.RecyclerView 12 | import com.haroldadmin.kshitijchauhan.resumade.R 13 | import com.haroldadmin.kshitijchauhan.resumade.adapter.EducationAdapter 14 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Education 15 | import com.haroldadmin.kshitijchauhan.resumade.ui.activities.CreateResumeActivity 16 | import com.haroldadmin.kshitijchauhan.resumade.utilities.areAllItemsSaved 17 | import com.haroldadmin.kshitijchauhan.resumade.utilities.invisible 18 | import com.haroldadmin.kshitijchauhan.resumade.utilities.visible 19 | import com.haroldadmin.kshitijchauhan.resumade.viewmodel.CreateResumeViewModel 20 | import kotlinx.android.synthetic.main.fragment_education.* 21 | 22 | class EducationFragment : Fragment() { 23 | 24 | private lateinit var educationAdapter: EducationAdapter 25 | private lateinit var linearLayoutManager: LinearLayoutManager 26 | private lateinit var createResumeViewModel: CreateResumeViewModel 27 | private lateinit var educationRecyclerView: RecyclerView 28 | 29 | override fun onCreate(savedInstanceState: Bundle?) { 30 | super.onCreate(savedInstanceState) 31 | educationAdapter = EducationAdapter( 32 | { item: Education -> 33 | // On Save Button Clicked 34 | item.saved = true 35 | createResumeViewModel.updateEducation(item) 36 | }, 37 | { item: Education -> 38 | // On Delete Button Clicked 39 | createResumeViewModel.deleteEducation(item) 40 | (activity as CreateResumeActivity).displaySnackbar("Education deleted.") 41 | }, 42 | { item: Education -> 43 | // On Edit Button Clicked 44 | item.saved = false 45 | createResumeViewModel.updateEducation(item) 46 | }) 47 | 48 | linearLayoutManager = LinearLayoutManager(context) 49 | } 50 | 51 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 52 | return inflater.inflate(R.layout.fragment_education, container, false) 53 | } 54 | 55 | override fun onActivityCreated(savedInstanceState: Bundle?) { 56 | super.onActivityCreated(savedInstanceState) 57 | activity?.let { 58 | createResumeViewModel = ViewModelProviders 59 | .of(it) 60 | .get(CreateResumeViewModel::class.java) 61 | } 62 | 63 | createResumeViewModel.educationList 64 | .observe(viewLifecycleOwner, Observer { 65 | educationAdapter.updateEducationList(it ?: emptyList()) 66 | createResumeViewModel.educationDetailsSaved = it == null || it.isEmpty() || it.areAllItemsSaved() 67 | toggleNoEducationLayout(it?.size ?: 0) 68 | }) 69 | } 70 | 71 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 72 | super.onViewCreated(view, savedInstanceState) 73 | 74 | educationRecyclerView = view.findViewById(R.id.educationRecyclerView) 75 | 76 | educationRecyclerView.apply { 77 | adapter = educationAdapter 78 | layoutManager = linearLayoutManager 79 | } 80 | } 81 | 82 | private fun toggleNoEducationLayout(size: Int) { 83 | if (size > 0) { 84 | educationRecyclerView.visible() 85 | noEducationView.invisible() 86 | } else { 87 | educationRecyclerView.invisible() 88 | noEducationView.visible() 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/ui/fragments/ExperienceFragment.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.ui.fragments 2 | 3 | import androidx.lifecycle.Observer 4 | import androidx.lifecycle.ViewModelProviders 5 | import android.os.Bundle 6 | import androidx.fragment.app.Fragment 7 | import androidx.recyclerview.widget.LinearLayoutManager 8 | import android.view.LayoutInflater 9 | import android.view.View 10 | import android.view.ViewGroup 11 | import com.haroldadmin.kshitijchauhan.resumade.R 12 | import com.haroldadmin.kshitijchauhan.resumade.adapter.ExperienceAdapter 13 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Experience 14 | import com.haroldadmin.kshitijchauhan.resumade.ui.activities.CreateResumeActivity 15 | import com.haroldadmin.kshitijchauhan.resumade.utilities.* 16 | import com.haroldadmin.kshitijchauhan.resumade.viewmodel.CreateResumeViewModel 17 | import kotlinx.android.synthetic.main.fragment_experience.* 18 | 19 | class ExperienceFragment : Fragment() { 20 | 21 | private lateinit var experienceAdapter : ExperienceAdapter 22 | private lateinit var linearLayoutManager : LinearLayoutManager 23 | private lateinit var createResumeViewModel : CreateResumeViewModel 24 | 25 | override fun onCreate(savedInstanceState: Bundle?) { 26 | super.onCreate(savedInstanceState) 27 | experienceAdapter = ExperienceAdapter( 28 | { item: Experience -> 29 | // On Save Button Click 30 | item.saved = true 31 | createResumeViewModel.apply { 32 | updateExperience(item) 33 | } 34 | }, 35 | { item: Experience -> 36 | // On Delete Button Click 37 | createResumeViewModel.deleteExperience(item) 38 | (activity as CreateResumeActivity).displaySnackbar("Experience deleted.") 39 | }, 40 | { item: Experience -> 41 | // On Edit Button Click 42 | item.saved = false 43 | createResumeViewModel.apply { 44 | updateExperience(item) 45 | } 46 | }) 47 | linearLayoutManager = LinearLayoutManager(context) 48 | } 49 | 50 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 51 | return inflater.inflate(R.layout.fragment_experience, container, false) 52 | } 53 | 54 | override fun onActivityCreated(savedInstanceState: Bundle?) { 55 | super.onActivityCreated(savedInstanceState) 56 | activity?.let { 57 | createResumeViewModel = ViewModelProviders 58 | .of(it) 59 | .get(CreateResumeViewModel::class.java) 60 | } 61 | 62 | createResumeViewModel.experienceList 63 | .observe(viewLifecycleOwner, Observer { 64 | experienceAdapter.updateExperienceList(it ?: emptyList()) 65 | createResumeViewModel.experienceDetailsSaved = it == null || it.isEmpty() || it.areAllItemsSaved() 66 | toggleNoExperienceLayout(it?.size ?: 0) 67 | }) 68 | } 69 | 70 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 71 | super.onViewCreated(view, savedInstanceState) 72 | experienceRecyclerView.apply { 73 | adapter = experienceAdapter 74 | layoutManager = linearLayoutManager 75 | } 76 | } 77 | 78 | private fun toggleNoExperienceLayout(size : Int) { 79 | if (size > 0) { 80 | experienceRecyclerView.visible() 81 | noExperienceView.invisible() 82 | } else { 83 | experienceRecyclerView.invisible() 84 | noExperienceView.visible() 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/ui/fragments/PersonalFragment.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.ui.fragments 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.lifecycle.Observer 9 | import androidx.lifecycle.ViewModelProviders 10 | import com.google.android.material.textfield.TextInputLayout 11 | import com.haroldadmin.kshitijchauhan.resumade.R 12 | import com.haroldadmin.kshitijchauhan.resumade.R.id.* 13 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Resume 14 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.ResumeEntity 15 | import com.haroldadmin.kshitijchauhan.resumade.utilities.inputvalidator.* 16 | import com.haroldadmin.kshitijchauhan.resumade.utilities.inputvalidator.validators.TextValidator 17 | import com.haroldadmin.kshitijchauhan.resumade.utilities.showKeyboard 18 | import com.haroldadmin.kshitijchauhan.resumade.viewmodel.CreateResumeViewModel 19 | import kotlinx.android.synthetic.main.fragment_personal.* 20 | 21 | class PersonalFragment : Fragment() { 22 | 23 | private lateinit var createResumeViewModel: CreateResumeViewModel 24 | private lateinit var resume: Resume 25 | 26 | private var tempResumeName = "" 27 | private var tempName = "" 28 | private var tempEmail = "" 29 | private var tempPhone = "" 30 | private var tempCity = "" 31 | private var tempSkills = "" 32 | private var tempHobbies = "" 33 | private var tempDescription = "" 34 | 35 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 36 | super.onCreateView(inflater, container, savedInstanceState) 37 | return inflater.inflate(R.layout.fragment_personal, container, false) 38 | } 39 | 40 | override fun onActivityCreated(savedInstanceState: Bundle?) { 41 | super.onActivityCreated(savedInstanceState) 42 | 43 | createResumeViewModel = ViewModelProviders 44 | .of(activity!!) 45 | .get(CreateResumeViewModel::class.java) 46 | 47 | createResumeViewModel.resume 48 | .observe(viewLifecycleOwner, Observer { 49 | this.resume = it ?: Resume.emptyResume() 50 | fillTextFieldsWithData(this.resume) 51 | adjustSaveButton(this.resume.saved) 52 | createResumeViewModel.personalDetailsSaved = this.resume.saved 53 | }) 54 | } 55 | 56 | private fun fillTextFieldsWithData(resume: Resume) { 57 | with(resume) { 58 | personalNameEditText.setText(name) 59 | personalPhoneEditText.setText(phone) 60 | personalEmailEditText.setText(email) 61 | personalCityEditText.setText(currentCity) 62 | personalSkillsEditText.setText(skills) 63 | personalHobbiesEditText.setText(hobbies) 64 | personalDescriptionEditText.setText(description) 65 | resumeNameEditText.setText(resumeName) 66 | } 67 | } 68 | 69 | private fun adjustSaveButton(resumeSavedStatus: Boolean) { 70 | personalSaveButton.apply { 71 | if (resumeSavedStatus) { 72 | // Edit Mode 73 | this.text = context.resources.getString(R.string.editButtonText) 74 | disableTextFields() 75 | this.setOnClickListener { 76 | enableTextFields() 77 | onEditButtonClicked(resume) 78 | this.text = context.resources.getString(R.string.saveButtonText) 79 | } 80 | } else { 81 | // Save Mode 82 | this.text = context.resources.getString(R.string.saveButtonText) 83 | setOnClickListener { 84 | if (runChecks()) { 85 | resume.apply { 86 | resumeName = tempResumeName 87 | name = tempName 88 | phone = tempPhone 89 | email = tempEmail 90 | currentCity = tempCity 91 | skills = tempSkills 92 | hobbies = tempHobbies 93 | description = tempDescription 94 | } 95 | onSaveButtonClick(resume) 96 | disableTextFields() 97 | this.text = context.resources.getString(R.string.editButtonText) 98 | } 99 | } 100 | } 101 | } 102 | } 103 | 104 | 105 | private fun onSaveButtonClick(item: T) { 106 | item.saved = true 107 | createResumeViewModel.apply { 108 | updateResume(item as Resume) 109 | } 110 | } 111 | 112 | private fun onEditButtonClicked(item: T) { 113 | item.saved = false 114 | createResumeViewModel.apply { 115 | updateResume(item as Resume) 116 | } 117 | } 118 | 119 | private fun runChecks(): Boolean { 120 | tempResumeName = resumeNameEditText.text.toString() 121 | tempName = personalNameEditText.text.toString() 122 | tempPhone = personalPhoneEditText.text.toString() 123 | tempEmail = personalEmailEditText.text.toString() 124 | tempCity = personalCityEditText.text.toString() 125 | tempSkills = personalSkillsEditText.text.toString() 126 | tempHobbies = personalHobbiesEditText.text.toString() 127 | tempDescription = personalDescriptionEditText.text.toString() 128 | 129 | arrayOf, TextInputLayout>>( 130 | tempName.addValidation() 131 | .required("Please enter your name") 132 | .validate() to personalNameWrapper, 133 | 134 | tempPhone.addValidation() 135 | .required("Please enter your phone number") 136 | .validate() to personalPhoneWrapper, 137 | 138 | tempEmail.addValidation() 139 | .required("Please enter your email address") 140 | .matches( 141 | "^[\\w.-]+@([\\w\\-]+\\.)+[A-Z]{2,4}$".toRegex(RegexOption.IGNORE_CASE), 142 | "Please enter a valid email" 143 | ) 144 | .validate() to personalEmailWrapper, 145 | 146 | tempCity.addValidation() 147 | .required("Please enter a city") 148 | .validate() to personalCityWrapper, 149 | 150 | 151 | tempHobbies.addValidation() 152 | .required("Please enter your hobbies") 153 | .validate() to personalHobbiesWrapper, 154 | 155 | 156 | tempSkills.addValidation() 157 | .required("Please enter your skills") 158 | .validate() to personalSkillsWrapper, 159 | 160 | tempDescription.addValidation() 161 | .required("Please describe yourself") 162 | .minLength(20, "Please add at least 20 characters") 163 | .validate() to personalDescriptionWrapper, 164 | 165 | 166 | tempResumeName.addValidation() 167 | .required("Resume name is required") 168 | .matches("^[a-zA-Z0-9 ]*$".toRegex(RegexOption.IGNORE_CASE), "Can contain only alphabets and numbers") 169 | .validate() to resumeNameWrapper 170 | ) 171 | .validateAll() 172 | .also { result -> 173 | return result 174 | } 175 | } 176 | 177 | private fun toggleTextFields(value: Boolean) { 178 | personalNameWrapper.isEnabled = value 179 | personalEmailWrapper.isEnabled = value 180 | personalPhoneWrapper.isEnabled = value 181 | personalCityWrapper.isEnabled = value 182 | personalSkillsWrapper.isEnabled = value 183 | personalHobbiesWrapper.isEnabled = value 184 | personalDescriptionWrapper.isEnabled = value 185 | resumeNameWrapper.isEnabled = value 186 | } 187 | 188 | private fun enableTextFields() { 189 | toggleTextFields(true) 190 | personalNameWrapper.showKeyboard(context) 191 | } 192 | 193 | private fun disableTextFields() = toggleTextFields(false) 194 | } -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/ui/fragments/ProjectsFragment.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.ui.fragments 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.lifecycle.Observer 9 | import androidx.lifecycle.ViewModelProviders 10 | import androidx.recyclerview.widget.LinearLayoutManager 11 | import com.haroldadmin.kshitijchauhan.resumade.R 12 | import com.haroldadmin.kshitijchauhan.resumade.adapter.ProjectAdapter 13 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Project 14 | import com.haroldadmin.kshitijchauhan.resumade.ui.activities.CreateResumeActivity 15 | import com.haroldadmin.kshitijchauhan.resumade.utilities.areAllItemsSaved 16 | import com.haroldadmin.kshitijchauhan.resumade.utilities.invisible 17 | import com.haroldadmin.kshitijchauhan.resumade.utilities.visible 18 | import com.haroldadmin.kshitijchauhan.resumade.viewmodel.CreateResumeViewModel 19 | import kotlinx.android.synthetic.main.fragment_projects.* 20 | 21 | class ProjectsFragment : Fragment() { 22 | 23 | private val TAG : String = this::class.java.simpleName 24 | private lateinit var projectAdapter: ProjectAdapter 25 | private lateinit var linearLayoutManager: androidx.recyclerview.widget.LinearLayoutManager 26 | private lateinit var createResumeViewModel: CreateResumeViewModel 27 | private lateinit var projectRecyclerView : androidx.recyclerview.widget.RecyclerView 28 | 29 | override fun onCreate(savedInstanceState: Bundle?) { 30 | super.onCreate(savedInstanceState) 31 | projectAdapter = ProjectAdapter( 32 | { item: Project -> 33 | item.saved = true 34 | createResumeViewModel.apply { 35 | updateProject(item) 36 | projectDetailsSaved = true 37 | } 38 | }, 39 | { item: Project -> 40 | createResumeViewModel.deleteProject(item) 41 | (activity as CreateResumeActivity).displaySnackbar("Project deleted.") 42 | }, 43 | { item: Project -> 44 | item.saved = false 45 | createResumeViewModel.apply { 46 | updateProject(item) 47 | projectDetailsSaved = false 48 | } 49 | }) 50 | linearLayoutManager = LinearLayoutManager(context) 51 | } 52 | 53 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 54 | return inflater.inflate(R.layout.fragment_projects, container, false) 55 | } 56 | 57 | override fun onActivityCreated(savedInstanceState: Bundle?) { 58 | super.onActivityCreated(savedInstanceState) 59 | activity?.let { 60 | createResumeViewModel = ViewModelProviders 61 | .of(it) 62 | .get(CreateResumeViewModel::class.java) 63 | } 64 | 65 | createResumeViewModel.projectsList 66 | .observe(viewLifecycleOwner, Observer { 67 | projectAdapter.updateProjectList(it ?: emptyList()) 68 | createResumeViewModel.projectDetailsSaved = it == null || it.isEmpty() || it.areAllItemsSaved() 69 | toggleNoProjectsLayout(it?.size ?: 0) 70 | }) 71 | } 72 | 73 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 74 | super.onViewCreated(view, savedInstanceState) 75 | projectRecyclerView = view.findViewById(R.id.projectsRecyclerView) 76 | projectRecyclerView.apply { 77 | adapter = projectAdapter 78 | layoutManager = linearLayoutManager 79 | } 80 | } 81 | 82 | private fun toggleNoProjectsLayout(size : Int) { 83 | if (size > 0) { 84 | projectRecyclerView.visible() 85 | noProjectsView.invisible() 86 | } else { 87 | projectRecyclerView.invisible() 88 | noProjectsView.visible() 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/utilities/AppDispatchers.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.utilities 2 | 3 | import kotlinx.coroutines.Dispatchers 4 | import kotlinx.coroutines.asCoroutineDispatcher 5 | import java.util.concurrent.ExecutorService 6 | import java.util.concurrent.Executors 7 | 8 | object AppDispatchers { 9 | 10 | /* 11 | A single threaded executor for handling database operations 12 | Using just one thread to enable sequential read/writes to the 13 | database in order to avoid race conditions 14 | */ 15 | private val diskIO : ExecutorService = Executors.newSingleThreadExecutor() 16 | 17 | val diskDispatcher = diskIO.asCoroutineDispatcher() 18 | val computationDispatcher = Dispatchers.Default 19 | val mainThreadDispatcher = Dispatchers.Main 20 | } 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/utilities/Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.utilities 2 | 3 | import android.content.Context 4 | import android.print.PrintAttributes 5 | import android.print.PrintManager 6 | import androidx.appcompat.app.AppCompatActivity 7 | import android.view.View 8 | import android.view.inputmethod.InputMethodManager 9 | import android.webkit.WebView 10 | import android.widget.EditText 11 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.ResumeEntity 12 | import com.haroldadmin.kshitijchauhan.resumade.utilities.inputvalidator.Text 13 | import com.haroldadmin.kshitijchauhan.resumade.utilities.inputvalidator.validators.TextValidator 14 | 15 | 16 | fun View.showKeyboard(context: Context?) { 17 | this.requestFocus() 18 | val inputMethodManager = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager 19 | inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, InputMethodManager.HIDE_IMPLICIT_ONLY) 20 | } 21 | 22 | fun AppCompatActivity.hideKeyboard() { 23 | val inputManager = this.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager 24 | inputManager.hideSoftInputFromWindow( 25 | if (this.currentFocus == null) { 26 | null 27 | } else { 28 | this.currentFocus.windowToken 29 | }, InputMethodManager.HIDE_NOT_ALWAYS) 30 | } 31 | 32 | fun List.isAnyItemUnsaved() : Boolean { 33 | for (entity in this) { 34 | if (!entity.saved) { 35 | return true 36 | } 37 | } 38 | return false 39 | } 40 | 41 | fun List.areAllItemsSaved() : Boolean = !this.isAnyItemUnsaved() 42 | 43 | fun WebView.createPrintJob(context: Context) { 44 | val printManager = context.getSystemService(Context.PRINT_SERVICE) as PrintManager 45 | val printAdapter = this.createPrintDocumentAdapter("Resumade document") 46 | val printJob = printManager.print("Resumade Job", printAdapter, PrintAttributes.Builder().build()) 47 | } 48 | 49 | fun View.gone() { 50 | this.visibility = View.GONE 51 | } 52 | 53 | fun View.visible() { 54 | this.visibility = View.VISIBLE 55 | } 56 | 57 | fun View.invisible() { 58 | this.visibility = View.INVISIBLE 59 | } -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/utilities/HtmlBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.utilities 2 | 3 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Education 4 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Experience 5 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Project 6 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Resume 7 | 8 | fun createBaseHTML() : String { 9 | var html = "" 10 | html += "\n" + 11 | "\n" + 12 | "\n" + 13 | " Resume\n" + 14 | "\n" + "" + 90 | "" + 91 | "\n" 92 | 93 | return html 94 | } 95 | 96 | fun addPersonalInfo(resume: Resume) : String { 97 | var html = "" 98 | if (resume.isNotEmpty()) { 99 | html += "
\n" + 100 | "
\n" + 101 | "

${resume.name.toUpperCase()}

\n" + 102 | "
\n" + 103 | "
\n" + 104 | "

${resume.phone}

\n" + 105 | "

${resume.email}

\n" + 106 | "

${resume.currentCity}

\n" + 107 | "
\n" + 108 | "
\n" + 109 | "
\n" + 110 | "

${resume.description}

\n" + 111 | "
\n" + 112 | "
\n" + 113 | "
" 114 | } 115 | return html 116 | } 117 | 118 | fun addEducationInfo(educationList: List) : String { 119 | var html = "" 120 | if (!educationList.isEmpty() && educationList.first().isNotEmpty()) { 121 | html += "
\n" + 122 | "

Education

\n" + 123 | "
" 124 | for (education in educationList) { 125 | if (education.isNotEmpty()) { 126 | html += "
\n" + 127 | "
\n" + 128 | "
\n" + 129 | "

${education.instituteName}

\n" + 130 | "
\n" + 131 | "
\n" + 132 | "

${education.degree}

\n" + 133 | "

${education.performance}

\n" + 134 | "

${education.year}

\n" + 135 | "
\n" + 136 | "
\n" + 137 | "
" 138 | } 139 | } 140 | html += "
\n" + 141 | "
" 142 | } 143 | return html 144 | } 145 | 146 | fun addExperienceInfo(experienceList : List) : String { 147 | var html = "" 148 | if (!experienceList.isEmpty() && experienceList.first().isNotEmpty()) { 149 | html += "
\n" + 150 | "
\n" + 151 | "

Experience

\n" + 152 | "
" 153 | for (experience in experienceList) { 154 | if (experience.isNotEmpty()) { 155 | html += "
\n" + 156 | "
\n" + 157 | "
\n" + 158 | "

${experience.companyName}

\n" + 159 | "
\n" + 160 | "
\n" + 161 | "

${experience.jobTitle}

\n" + 162 | "

${experience.duration}

\n" + 163 | "
\n" + 164 | "
\n" + 165 | "
" 166 | } 167 | } 168 | 169 | html += "
\n" + 170 | "
\n" + 171 | "
" 172 | } 173 | return html 174 | } 175 | 176 | fun addProjectInfo(projectsList : List) : String { 177 | var html = "" 178 | if (!projectsList.isEmpty() && projectsList.first().isNotEmpty()) { 179 | html += "
\n" + 180 | "

Projects

\n" + 181 | "
" 182 | for (project in projectsList) { 183 | if (project.isNotEmpty()) { 184 | html += "
\n" + 185 | "
\n" + 186 | "
\n" + 187 | "

${project.projectName}

\n" + 188 | "
\n" + 189 | "
\n" + 190 | "

${project.role}

\n" 191 | // We need to add this check because projectLink might be empty 192 | if (!project.link.isEmpty()) { 193 | html += "

${project.link}

\n" 194 | } 195 | html += 196 | "

${project.description}

\n" + 197 | "
\n" + 198 | "
\n" + 199 | "
" 200 | } 201 | 202 | html += "
\n" + 203 | "
\n" + 204 | "
" 205 | } 206 | } 207 | return html 208 | } 209 | 210 | fun addSkills(resume : Resume) : String { 211 | var html = "" 212 | if (resume.skills != "") { 213 | val skillsList = resume.skills.split(",") 214 | skillsList.map { it -> it.trim() } 215 | if (skillsList.isNotEmpty() && skillsList.first().isNotBlank()) { 216 | html += "
\n" + 217 | "
\n" + 218 | "

Skills

\n" + 219 | "
\n" + 220 | "
\n" + 221 | "
    " 222 | for (skill in skillsList) { 223 | if (skill.isNotBlank()) { 224 | html += "
  • $skill
  • " 225 | } 226 | } 227 | 228 | html += "
\n" + 229 | "
\n" + 230 | "
\n" + 231 | "
" 232 | } 233 | } 234 | return html 235 | } 236 | 237 | fun addHobbies(resume : Resume) : String { 238 | var html = "" 239 | if (resume.hobbies != "") { 240 | val hobbiesList = resume.hobbies.split(",") 241 | hobbiesList.map { it -> it.trim() } 242 | if (hobbiesList.isNotEmpty() && hobbiesList.first().isNotBlank()) { 243 | html += "
\n" + 244 | "
\n" + 245 | "

Hobbies

\n" + 246 | "
\n" + 247 | "
\n" + 248 | "
    " 249 | for (hobby in hobbiesList) { 250 | if (hobby.isNotBlank()) { 251 | html += "
  • $hobby
  • " 252 | } 253 | } 254 | 255 | html += "
\n" + 256 | "
\n" + 257 | "
" 258 | } 259 | } 260 | return html 261 | } 262 | 263 | fun closeHtmlFile() : String { 264 | var html = "" 265 | html += "\n" + 266 | "" 267 | return html 268 | } 269 | 270 | fun buildHtml(resume : Resume, educationList: List, experienceList: List, projectList: List) : String { 271 | var html = "" 272 | html += createBaseHTML() 273 | html += addPersonalInfo(resume) 274 | html += addEducationInfo(educationList) 275 | html += addExperienceInfo(experienceList) 276 | html += addProjectInfo(projectList) 277 | html += addSkills(resume) 278 | html += addHobbies(resume) 279 | html += closeHtmlFile() 280 | return html 281 | } -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/utilities/inputvalidator/Errors.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.utilities.inputvalidator 2 | 3 | sealed class TextError: InputError() { 4 | object Absent: TextError() 5 | object SizeMismatch: TextError() 6 | object TooLong: TextError() 7 | object TooShort: TextError() 8 | object DoesNotMatchRegExp: TextError() 9 | } 10 | 11 | sealed class NumberError: InputError() { 12 | object Absent: NumberError() 13 | object TooBig: NumberError() 14 | object TooLittle: NumberError() 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/utilities/inputvalidator/Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.utilities.inputvalidator 2 | 3 | import android.widget.EditText 4 | import com.google.android.material.textfield.TextInputLayout 5 | import com.haroldadmin.kshitijchauhan.resumade.utilities.inputvalidator.validators.TextValidator 6 | 7 | fun String.addValidation() = TextValidator(this) 8 | 9 | fun TextValidator.useWith(editText: EditText): TextValidator { 10 | when { 11 | this.errors.isEmpty() -> editText.error = null 12 | else -> this.errors.first() 13 | .let { 14 | editText.error = it.second 15 | } 16 | } 17 | return this 18 | } 19 | 20 | fun TextValidator.useWith(textInputLayout: TextInputLayout): TextValidator { 21 | when { 22 | this.errors.isEmpty() -> textInputLayout.apply { 23 | isErrorEnabled = false 24 | isHelperTextEnabled = false 25 | } 26 | else -> this.errors.first().let { 27 | textInputLayout.error = it.second 28 | } 29 | 30 | } 31 | return this 32 | } 33 | 34 | fun validateAllEditTexts(validations: Iterable, EditText>>): Boolean { 35 | var passed = true 36 | validations.map { (inputValidation, editText) -> 37 | inputValidation.validate() to editText 38 | }.map { (validatedInput, editText) -> 39 | validatedInput.errors.firstOrNull()?.let { 40 | editText.error = it.second 41 | passed = false 42 | } 43 | } 44 | return passed 45 | } 46 | 47 | fun Array, TextInputLayout>>.validateAll(): Boolean { 48 | var passed = true 49 | this.map { (inputValidation, til) -> 50 | inputValidation.validate() to til 51 | }.map { (validatedInput, til) -> 52 | validatedInput.errors.firstOrNull()?.let { 53 | til.error = it.second 54 | til.isHelperTextEnabled = true 55 | passed = false 56 | } ?: run { 57 | til.isErrorEnabled = false 58 | til.isHelperTextEnabled = false 59 | } 60 | } 61 | return passed 62 | } -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/utilities/inputvalidator/Properties.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.utilities.inputvalidator 2 | 3 | sealed class TextProperty(errorMessage: String): InputProperty(errorMessage) { 4 | class Required(errorMessage: String): TextProperty(errorMessage) 5 | class Length(val length: Int, errorMessage: String): TextProperty(errorMessage) 6 | class MinLength(val length: Int, errorMessage: String): TextProperty(errorMessage) 7 | class MaxLength(val length: Int, errorMessage: String): TextProperty(errorMessage) 8 | class RegularExp(val exp: Regex, errorMessage: String): TextProperty(errorMessage) 9 | } 10 | 11 | sealed class NumberProperty(errorMessage: String): InputProperty(errorMessage) { 12 | class Required(errorMessage: String): NumberProperty(errorMessage) 13 | class Min(errorMessage: String): NumberProperty(errorMessage) 14 | class Max(errorMessage: String): NumberProperty(errorMessage) 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/utilities/inputvalidator/Types.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.utilities.inputvalidator 2 | 3 | import com.haroldadmin.kshitijchauhan.resumade.utilities.inputvalidator.validators.TextValidator 4 | import kotlin.Number 5 | 6 | abstract class InputType 7 | 8 | abstract class InputProperty(val errorMessage: String) 9 | 10 | abstract class InputError 11 | 12 | class Text(val text: String): InputType() { 13 | override fun toString(): String { 14 | return text 15 | } 16 | } 17 | 18 | class Number(val number: Number): InputType() { 19 | override fun toString(): String { 20 | return number.toString() 21 | } 22 | } 23 | 24 | abstract class InputValidator (protected val input: T) { 25 | abstract fun validate(): TextValidator 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/utilities/inputvalidator/validators/TextValidator.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.utilities.inputvalidator.validators 2 | 3 | import com.haroldadmin.kshitijchauhan.resumade.utilities.inputvalidator.InputValidator 4 | import com.haroldadmin.kshitijchauhan.resumade.utilities.inputvalidator.Text 5 | import com.haroldadmin.kshitijchauhan.resumade.utilities.inputvalidator.TextError 6 | import com.haroldadmin.kshitijchauhan.resumade.utilities.inputvalidator.TextProperty 7 | 8 | class TextValidator(input: String) : InputValidator(Text(input)) { 9 | 10 | private val _errors: MutableSet> = HashSet() 11 | val errors: Set> 12 | get() = _errors 13 | 14 | private val _properties: MutableSet = HashSet() 15 | val properties: Set 16 | get() = _properties 17 | 18 | fun required(errorMessage: String): TextValidator { 19 | this._properties.add(TextProperty.Required(errorMessage)) 20 | return this 21 | } 22 | 23 | fun length(length: Int, errorMessage: String): TextValidator { 24 | this._properties.add(TextProperty.Length(length, errorMessage)) 25 | return this 26 | } 27 | 28 | fun minLength(length: Int, errorMessage: String): TextValidator { 29 | this._properties.add(TextProperty.MinLength(length, errorMessage)) 30 | return this 31 | } 32 | 33 | fun maxLength(length: Int, errorMessage: String): TextValidator { 34 | this._properties.add(TextProperty.MaxLength(length, errorMessage)) 35 | return this 36 | } 37 | 38 | fun matches(exp: Regex, errorMessage: String): TextValidator { 39 | this._properties.add(TextProperty.RegularExp(exp, errorMessage)) 40 | return this 41 | } 42 | 43 | fun matches(pattern: String, errorMessage: String): TextValidator { 44 | val exp = Regex(pattern) 45 | this._properties.add(TextProperty.RegularExp(exp, errorMessage)) 46 | return this 47 | } 48 | 49 | override fun validate(): TextValidator { 50 | val text = input.toString() 51 | this._properties.forEach { property -> 52 | when (property) { 53 | is TextProperty.Required -> 54 | if (text.isEmpty()) _errors.add(TextError.Absent to property.errorMessage) 55 | is TextProperty.Length -> 56 | if (text.length != property.length) _errors.add(TextError.SizeMismatch to property.errorMessage) 57 | is TextProperty.MinLength-> 58 | if (text.length < property.length) _errors.add(TextError.TooShort to property.errorMessage) 59 | is TextProperty.MaxLength -> 60 | if (text.length > property.length) _errors.add(TextError.TooLong to property.errorMessage) 61 | is TextProperty.RegularExp -> 62 | if (!text.matches(property.exp)) _errors.add(TextError.DoesNotMatchRegExp to property.errorMessage) 63 | } 64 | } 65 | return this 66 | } 67 | } -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/viewmodel/CreateResumeViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.viewmodel 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.AndroidViewModel 5 | import androidx.lifecycle.LiveData 6 | import com.haroldadmin.kshitijchauhan.resumade.repository.LocalRepository 7 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Education 8 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Experience 9 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Project 10 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Resume 11 | import com.haroldadmin.kshitijchauhan.resumade.ui.activities.CreateResumeActivity 12 | import kotlinx.coroutines.* 13 | 14 | class CreateResumeViewModel(application: Application) : AndroidViewModel(application), CoroutineScope { 15 | 16 | private val createResumeViewModelJob = Job() 17 | 18 | override val coroutineContext = Dispatchers.Main + createResumeViewModelJob 19 | 20 | private val TAG = this::class.java.simpleName 21 | private var resumeId: Long 22 | val resume: LiveData 23 | val educationList: LiveData> 24 | val experienceList: LiveData> 25 | val projectsList: LiveData> 26 | 27 | /* 28 | Initializing these values as true because 29 | the user might not want to add any of these 30 | details at all. These will be set to false 31 | upon the creation of an item in the respective 32 | categories, and then back to true when the item 33 | is saved. This also ensures that no empty item 34 | is saved too. 35 | */ 36 | var personalDetailsSaved: Boolean = true 37 | var educationDetailsSaved: Boolean = true 38 | var experienceDetailsSaved: Boolean = true 39 | var projectDetailsSaved: Boolean = true 40 | 41 | private var repository: LocalRepository = LocalRepository(getApplication()) 42 | 43 | init { 44 | resumeId = CreateResumeActivity.currentResumeId 45 | if (resumeId == -1L) { 46 | /* 47 | We can run this in a non blocking way 48 | because we don't care about the resumeId 49 | for a new resume as long as the user does 50 | not press the save button on the personal fragment, 51 | or the add button in any other fragments. Those events 52 | occur a long time after the viewmodel is created, 53 | so we can ignore the possibility of resumeId being -1L 54 | any time when it actually matters. 55 | */ 56 | runBlocking { 57 | resumeId = repository.insertResume(Resume("My Resume", "", "", "", "", "", "", "")) 58 | } 59 | } 60 | resume = repository.getResumeForId(resumeId) 61 | educationList = repository.getAllEducationForResume(resumeId) 62 | experienceList = repository.getAllExperienceForResume(resumeId) 63 | projectsList = repository.getAllProjectsForResume(resumeId) 64 | } 65 | 66 | fun insertBlankEducation() = launch { 67 | val education = Education("", "", "", "", resumeId) 68 | repository.insertEducation(education) 69 | } 70 | 71 | fun insertBlankExperience() = launch { 72 | val experience = Experience("", "", "", resumeId) 73 | repository.insertExperience(experience) 74 | } 75 | 76 | fun insertBlankProject() = launch { 77 | val project = Project("", "", "", "", resumeId) 78 | repository.insertProject(project) 79 | } 80 | 81 | fun updateResume(resume: Resume) = launch { 82 | resume.id = resumeId 83 | repository.updateResume(resume) 84 | } 85 | 86 | fun updateEducation(education: Education) = launch { 87 | repository.updateEducation(education) 88 | } 89 | 90 | fun updateExperience(experience: Experience) = launch { 91 | repository.updateExperience(experience) 92 | } 93 | 94 | fun updateProject(project: Project) = launch { 95 | repository.updateProject(project) 96 | } 97 | 98 | fun deleteEducation(education: Education) = launch { 99 | repository.deleteEducation(education) 100 | } 101 | 102 | fun deleteExperience(experience: Experience) = launch { 103 | repository.deleteExperience(experience) 104 | } 105 | 106 | fun deleteProject(project: Project) = launch { 107 | repository.deleteProject(project) 108 | } 109 | 110 | fun deleteTempResume() = launch { 111 | repository.deleteResumeForId(resumeId) 112 | } 113 | 114 | override fun onCleared() { 115 | super.onCleared() 116 | createResumeViewModelJob.cancel() 117 | } 118 | } -------------------------------------------------------------------------------- /app/src/main/java/com/haroldadmin/kshitijchauhan/resumade/viewmodel/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade.viewmodel 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.AndroidViewModel 5 | import androidx.lifecycle.LiveData 6 | import com.haroldadmin.kshitijchauhan.resumade.repository.LocalRepository 7 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Education 8 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Experience 9 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Project 10 | import com.haroldadmin.kshitijchauhan.resumade.repository.database.Resume 11 | import kotlinx.coroutines.CoroutineScope 12 | import kotlinx.coroutines.Dispatchers 13 | import kotlinx.coroutines.Job 14 | import kotlinx.coroutines.launch 15 | 16 | /* 17 | This ViewModel serves as the ViewModel for 18 | the main activity. The CreateResumeActivity has a 19 | separate viewmodel. 20 | */ 21 | 22 | class MainViewModel(application: Application) : AndroidViewModel(application), CoroutineScope { 23 | 24 | private val mainViewModelJob = Job() 25 | 26 | override val coroutineContext = Dispatchers.Main + mainViewModelJob 27 | 28 | private val TAG: String = this::class.java.simpleName 29 | 30 | /* 31 | Cached list of resume with a private setter 32 | so that it can only be read from other classes, 33 | but not modified. 34 | */ 35 | val resumesList: LiveData> 36 | /* 37 | The main view model extends AndroidViewModel, 38 | so it gets an instance of application in its constructor. 39 | We use this to initialize the repository, 40 | which in turn initializes the database. 41 | */ 42 | private var repository: LocalRepository = LocalRepository(getApplication()) 43 | 44 | init { 45 | resumesList = repository.getAllResume() 46 | } 47 | 48 | fun deleteResume(resume: Resume) = launch { 49 | repository.deleteResume(resume) 50 | } 51 | 52 | fun getResumeForId(resumeId: Long): Resume = repository.getSingleResumeForId(resumeId) 53 | 54 | fun getEducationForResume(resumeId: Long): List = repository.getAllEducationForResumeOnce(resumeId) 55 | 56 | fun getExperienceForResume(resumeId: Long): List = repository.getAllExperienceForResumeOnce(resumeId) 57 | 58 | fun getProjectForResume(resumeId: Long): List = repository.getAllProjectsForResumeOnce(resumeId) 59 | 60 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_github_circle.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_outline_info_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_add_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_code_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_delete_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_done_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_edit_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_open_in_new_24px.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_preview_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_print_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_rate_review_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_round_save_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/list_divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/web_hi_res_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haroldadmin/Resumade/0f480e2a0e1f3f675f07d41f517315fc06f9a4fe/app/src/main/res/drawable/web_hi_res_512.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_about_us.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 16 | 17 | 29 | 30 | 45 | 46 | 62 | 63 | 73 | 74 | 83 | 84 | 90 | 91 | 102 | 103 | 104 | 113 | 114 | 119 | 120 | 131 | 132 | 133 | 142 | 143 | 148 | 149 | 160 | 161 | 162 | 171 | 172 | 177 | 178 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_create_resume.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 22 | 23 | 28 | 29 | 30 | 31 | 36 | 37 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 26 | 27 | 34 | 35 | 36 | 37 | 38 | 40 | 41 | 46 | 47 | 58 | 59 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_preview.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/card_education.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 15 | 16 | 33 | 34 | 42 | 43 | 44 | 45 | 61 | 62 | 70 | 71 | 72 | 89 | 90 | 99 | 100 | 101 | 102 | 119 | 120 | 129 | 130 | 131 | 145 | 146 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /app/src/main/res/layout/card_experience.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 15 | 16 | 31 | 32 | 39 | 40 | 41 | 42 | 57 | 58 | 66 | 67 | 68 | 83 | 84 | 92 | 93 | 94 | 95 | 109 | 110 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /app/src/main/res/layout/card_project.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 16 | 17 | 32 | 33 | 41 | 42 | 43 | 44 | 59 | 60 | 68 | 69 | 70 | 85 | 86 | 93 | 94 | 95 | 96 | 111 | 112 | 119 | 120 | 121 | 122 | 136 | 137 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /app/src/main/res/layout/card_resume.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 23 | 24 | 35 | 36 | 47 | 48 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_education.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_experience.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_personal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 15 | 16 | 30 | 31 | 40 | 41 | 42 | 43 | 59 | 60 | 69 | 70 | 71 | 72 | 86 | 87 | 96 | 97 | 98 | 99 | 113 | 114 | 123 | 124 | 125 | 126 | 142 | 143 | 151 | 152 | 153 | 154 | 170 | 171 | 179 | 180 | 181 | 182 | 198 | 199 | 208 | 209 | 210 | 211 | 227 | 228 | 238 | 239 | 240 | 241 | 255 | 256 | 257 | 258 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_projects.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/no_items_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 22 | 23 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_create_resume_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 15 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haroldadmin/Resumade/0f480e2a0e1f3f675f07d41f517315fc06f9a4fe/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haroldadmin/Resumade/0f480e2a0e1f3f675f07d41f517315fc06f9a4fe/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haroldadmin/Resumade/0f480e2a0e1f3f675f07d41f517315fc06f9a4fe/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haroldadmin/Resumade/0f480e2a0e1f3f675f07d41f517315fc06f9a4fe/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haroldadmin/Resumade/0f480e2a0e1f3f675f07d41f517315fc06f9a4fe/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #3f89f4 5 | #7eb8ff 6 | #005dc1 7 | #eceff1 8 | #ffffff 9 | #babdbe 10 | #ffffff 11 | #000000 12 | #807eb8ff 13 | #80ffffff 14 | #D32F2F 15 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Resumade 3 | Nothing here.\nTap the add button! 4 | Create Resume 5 | Resume Name 6 | Name 7 | Phone 8 | Email Address 9 | Current City 10 | Description 11 | Skills 12 | Hobbies 13 | Save 14 | Institute Name 15 | Degree 16 | Year 17 | Performance 18 | Saved 19 | Company 20 | Job Title 21 | Duration 22 | Name 23 | Role 24 | Link (optional) 25 | Description 26 | Should be 10 digits 27 | E.g: Graphic Designing, Animation 28 | E.g: Running, Swimming 29 | Give this resume a fun name! 30 | Delete 31 | Done 32 | Print 33 | This project is completely open source. 34 | This app was created with first year engineering students as its target audience. 35 | You should check out Standard Resume if you need something more professional. 36 | Tell us about you! 37 | Name of the school or college 38 | Your degree, branch, or class from this institute 39 | GPA, percentage, or grade 40 | Year of graduation 41 | Name of the company where you worked 42 | Eg. Backend Web Developer 43 | Time period of this job 44 | Name of the project 45 | Your role in the project 46 | A website for the project 47 | Describe the project 48 | Edit 49 | About 50 | Preview 51 | Created by Kshitij Chauhan 52 | App Repository 53 | Dev Profile 54 | Standard Resume 55 | Rate the app 56 | 57 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 27 | 28 | 34 | 35 | 38 | 39 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/test/java/com/haroldadmin/kshitijchauhan/resumade/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.haroldadmin.kshitijchauhan.resumade 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.3.11' 5 | repositories { 6 | google() 7 | jcenter() 8 | maven { 9 | url 'https://maven.fabric.io/public' 10 | } 11 | } 12 | dependencies { 13 | classpath 'com.android.tools.build:gradle:3.2.1' 14 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 15 | classpath 'com.google.gms:google-services:4.2.0' 16 | classpath 'io.fabric.tools:gradle:1.26.1' 17 | // NOTE: Do not place your application dependencies here; they belong 18 | // in the individual module build.gradle files 19 | } 20 | } 21 | 22 | allprojects { 23 | repositories { 24 | google() 25 | jcenter() 26 | } 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | android.enableJetifier=true 10 | android.useAndroidX=true 11 | org.gradle.jvmargs=-Xmx1536m 12 | # When configured, Gradle will run in incubating parallel mode. 13 | # This option should only be used with decoupled projects. More details, visit 14 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 15 | # org.gradle.parallel=true 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haroldadmin/Resumade/0f480e2a0e1f3f675f07d41f517315fc06f9a4fe/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Oct 28 11:41:12 IST 2018 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.6-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------