├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── woodinvoicetest │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── woodinvoicetest │ │ │ ├── AddCustomerName.kt │ │ │ ├── Common.kt │ │ │ ├── ConfirmEverything.kt │ │ │ ├── DataClasses.kt │ │ │ ├── InvoiceReviewScreen.kt │ │ │ ├── MainActivity.kt │ │ │ ├── QuantityAndPrice.kt │ │ │ ├── SendToServer.kt │ │ │ ├── ShowInvoices.kt │ │ │ ├── ShowSingleInvoice.kt │ │ │ ├── WoodInvoicesDao.kt │ │ │ ├── WoodInvoicesDatabase.kt │ │ │ ├── WoodInvoicesRepository.kt │ │ │ ├── WoodInvoicesViewModel.kt │ │ │ ├── WoodInvoicesViewModelFactory.kt │ │ │ ├── WoodProducts.kt │ │ │ └── utils.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── background.jpg │ │ ├── button_shape.xml │ │ ├── choose_button.xml │ │ ├── ic_launcher_background.xml │ │ ├── my_cancel_button.xml │ │ ├── my_delete_icon.xml │ │ ├── my_edit_icon.xml │ │ └── subbackground.jpg │ │ ├── layout-land │ │ ├── activity_add_customer_name.xml │ │ ├── activity_confirm_everything.xml │ │ ├── activity_invoice_review_screen.xml │ │ ├── activity_main.xml │ │ ├── activity_quantity_and_price.xml │ │ └── activity_woodproducts.xml │ │ ├── layout-sw600dp │ │ ├── activity_add_customer_name.xml │ │ ├── activity_confirm_everything.xml │ │ ├── activity_invoice_review_screen.xml │ │ ├── activity_main.xml │ │ ├── activity_quantity_and_price.xml │ │ └── activity_woodproducts.xml │ │ ├── layout │ │ ├── activity_add_customer_name.xml │ │ ├── activity_confirm_everything.xml │ │ ├── activity_invoice_review_screen.xml │ │ ├── activity_main.xml │ │ ├── activity_quantity_and_price.xml │ │ ├── activity_send_to_server.xml │ │ ├── activity_show_invoices.xml │ │ ├── activity_show_single_invoice.xml │ │ ├── activity_woodproducts.xml │ │ └── alert_dialog_with_edittext.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── my_ic_launcher.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── my_ic_launcher.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── my_ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── my_ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── my_ic_launcher.png │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── network_security_config.xml │ └── test │ └── java │ └── com │ └── example │ └── woodinvoicetest │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.aar 4 | *.ap_ 5 | *.aab 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | # Uncomment the following line in case you need and you don't have the release build type files in your app 18 | # release/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # IntelliJ 40 | *.iml 41 | .idea/ 42 | .idea/workspace.xml 43 | .idea/tasks.xml 44 | .idea/gradle.xml 45 | .idea/assetWizardSettings.xml 46 | .idea/dictionaries 47 | .idea/libraries 48 | # Android Studio 3 in .gitignore file. 49 | .idea/caches 50 | .idea/modules.xml 51 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 52 | .idea/navEditor.xml 53 | 54 | # Keystore files 55 | # Uncomment the following lines if you do not want to check your keystore files in. 56 | #*.jks 57 | #*.keystore 58 | 59 | # External native build folder generated in Android Studio 2.2 and later 60 | .externalNativeBuild 61 | .cxx/ 62 | 63 | # Google Services (e.g. APIs or Firebase) 64 | # google-services.json 65 | 66 | # Freeline 67 | freeline.py 68 | freeline/ 69 | freeline_project_description.json 70 | 71 | # fastlane 72 | fastlane/report.xml 73 | fastlane/Preview.html 74 | fastlane/screenshots 75 | fastlane/test_output 76 | fastlane/readme.md 77 | 78 | # Version control 79 | vcs.xml 80 | 81 | # lint 82 | lint/intermediates/ 83 | lint/generated/ 84 | lint/outputs/ 85 | lint/tmp/ 86 | # lint/reports/ 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Take Orders APP 2 | It is an Android App that I wrote to take orders from the customers and establish invoices. 3 | It is similar to the applications which restaurants use to get food orders from the visitors. 4 | I designed the app for our family company that sells sawn wood and plywood. 5 | The wood products have width, thickness, and length as main properties. 6 | The products can have extra specific features such as large or small, wet or dry, etc. 7 | I wrote the App in Kotlin 8 | 9 | ## Requirements and installation 10 | 11 | 1. You need to download [Android Studio](https://developer.android.com/studio) 12 | 2. Clone the Repo 13 | 14 | ```shell script 15 | $ git clone https://github.com/MaherDeeb/take_orders_app.git 16 | ``` 17 | 3. Build APK 18 | 19 | ## App flow: 20 | 21 | 22 | Check the Demo [here](https://youtu.be/8MdLv3h-j-o) 23 | 24 | ## Next Step 25 | 26 | The App is a part of a data pipeline. The pipeline contains several components: 27 | 28 | 1. Wood orders taker App (this app): An android app that the workers use to collect orders from the customers. After confirming the payment, the App sends the data to the remote server. 29 | 2. Python-based remote server: It is a simple VM that receives and stores the data in the database. It sends a notification to keep the manger up to date after every transaction. 30 | 3. Invoices viewer App: It is a simple android app that the manager uses to view the notifications which the server sends. The manager can fetch the data from the server and check the invoices. 31 | 4. CenterAI: it is a computer vision framework that I wrote in Python on top of Pytorch. CenterAI enables training models for computer vision similar to other frameworks such as fastai but with a more convenient interface. 32 | 5. Wood QQ (not sure about the final name yet) App: It is an android app that the customer can use to count the sawn wood pieces and measure the wood quality from images. 33 | 6. An experimental environment to analyze the data. The data should answer some important business questions such as the next best offer for each customer, the customer lifetime value, and so on. 34 | 7. A simple dashboard helps the manager observe how the company does in the short and long terms. 35 | 36 | For more information you, check my [article](https://www.linkedin.com/pulse/ai-value-season-1-episode-maher-deeb) 37 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-kapt' 4 | apply plugin: 'kotlin-android-extensions' 5 | 6 | android { 7 | compileSdkVersion 29 8 | buildToolsVersion "29.0.3" 9 | 10 | defaultConfig { 11 | applicationId "com.example.woodinvoicetest" 12 | minSdkVersion 16 13 | targetSdkVersion 29 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | 27 | dataBinding { 28 | enabled = true 29 | } 30 | 31 | } 32 | 33 | dependencies { 34 | implementation fileTree(dir: 'libs', include: ['*.jar']) 35 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 36 | implementation 'androidx.appcompat:appcompat:1.1.0' 37 | implementation 'androidx.core:core-ktx:1.3.0' 38 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 39 | testImplementation 'junit:junit:4.12' 40 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 41 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 42 | implementation 'androidx.gridlayout:gridlayout:1.0.0' 43 | implementation "org.jetbrains.kotlin:kotlin-script-runtime:1.3.72" 44 | implementation 'com.karumi:dexter:6.1.2' 45 | implementation 'com.itextpdf:itextg:5.5.10' 46 | // Room and Lifecycle dependencies 47 | //noinspection GradleDependency 48 | implementation "androidx.room:room-runtime:$version_room" 49 | kapt "androidx.room:room-compiler:$version_room" 50 | //noinspection GradleDependency 51 | implementation "androidx.lifecycle:lifecycle-extensions:$version_lifecycle_extensions" 52 | //noinspection GradleDependency 53 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$version_lifecycle_extensions" 54 | //noinspection GradleDependency 55 | // implementation "androidx.lifecycle:lifecycle-livedata-ktx:$version_lifecycle_extensions" 56 | //noinspection LifecycleAnnotationProcessorWithJava8 57 | kapt "androidx.lifecycle:lifecycle-compiler:$version_lifecycle_extensions" 58 | kapt "com.android.databinding:compiler:3.6.3" 59 | implementation 'com.jakewharton.threetenabp:threetenabp:1.2.1' 60 | // Coroutines 61 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version_coroutine" 62 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$version_coroutine" 63 | implementation 'com.google.code.gson:gson:2.8.5' 64 | implementation 'com.android.volley:volley:1.1.1' 65 | //noinspection GradleDependency 66 | implementation "com.google.android.material:material:1.3.0-alpha01" 67 | } 68 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/woodinvoicetest/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.woodinvoicetest 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.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.getInstrumentation().targetContext 22 | assertEquals("com.example.woodinvoicetest", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/woodinvoicetest/AddCustomerName.kt: -------------------------------------------------------------------------------- 1 | package com.example.woodinvoicetest 2 | 3 | import android.content.DialogInterface 4 | import android.content.Intent 5 | import androidx.appcompat.app.AppCompatActivity 6 | import android.os.Bundle 7 | import android.text.Editable 8 | import android.text.TextWatcher 9 | import android.widget.Toast 10 | import androidx.appcompat.app.AlertDialog 11 | import kotlinx.android.synthetic.main.activity_add_customer_name.* 12 | import java.util.* 13 | import kotlin.math.round 14 | 15 | class AddCustomerName : AppCompatActivity() { 16 | 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | setContentView(R.layout.activity_add_customer_name) 20 | totalSum.text = decimalFormatChanger.format(ordersInvoice.invoiceTotalSum).toString() 21 | 22 | 23 | soldInputs.addTextChangedListener(object : TextWatcher { 24 | override fun afterTextChanged(s: Editable) {} 25 | override fun beforeTextChanged( 26 | s: CharSequence, start: Int, 27 | count: Int, after: Int 28 | ) { 29 | } 30 | 31 | override fun onTextChanged( 32 | s: CharSequence, start: Int, 33 | before: Int, count: Int 34 | ) { 35 | if (s.isNotEmpty()) { 36 | var totalSumWithSold = ordersInvoice.invoiceTotalSum - s.toString().toDouble() 37 | totalSumWithSold = round(totalSumWithSold * 100) / 100 38 | totalSum.text = decimalFormatChanger.format(totalSumWithSold).toString() 39 | 40 | } 41 | } 42 | }) 43 | confirmPayment.setOnClickListener { goToConfirmEverything() } 44 | cancelInvoiceButton.setOnClickListener { resetInvoiceYesNoDialog() } 45 | backButton.setOnClickListener { goToInvoiceReviewScreen() } 46 | } 47 | 48 | private fun goToConfirmEverything() { 49 | if (customerNameInput.text.isNotEmpty()) { 50 | ordersInvoice.customerName = customerNameInput.text.toString() 51 | ordersInvoice.invoiceId = getAndShowDate() 52 | if (soldInputs.text.isEmpty()) { 53 | ordersInvoice.invoiceSold = 0.0 54 | } else { 55 | ordersInvoice.invoiceSold = soldInputs.text.toString().toDouble() 56 | } 57 | 58 | ordersInvoice.invoiceNotes = invoiceNotesEditText.text.toString() 59 | 60 | val intent = Intent(this, ConfirmEverything::class.java) 61 | startActivity(intent) 62 | } else { 63 | Toast.makeText( 64 | this, 65 | getString(R.string.missingCustomerNameMessageError), 66 | Toast.LENGTH_SHORT 67 | ).show() 68 | } 69 | } 70 | 71 | private fun cancelInvoice() { 72 | ordersInvoice = InvoiceObjectClass() 73 | orderedProduct = OrderedProduct() 74 | val intent = Intent(this, MainActivity::class.java) 75 | startActivity(intent) 76 | } 77 | 78 | private fun resetInvoiceYesNoDialog() { 79 | lateinit var dialog: AlertDialog 80 | val builder = AlertDialog.Builder(this) 81 | builder.setTitle(getString(R.string.cancelInvoiceMessageTitle)) 82 | builder.setMessage(getString(R.string.doYouWantToCancelInvoiceMessage)) 83 | val dialogClickListener = DialogInterface.OnClickListener { _, which -> 84 | when (which) { 85 | DialogInterface.BUTTON_POSITIVE -> cancelInvoice() 86 | DialogInterface.BUTTON_NEGATIVE -> doNothing() 87 | } 88 | } 89 | builder.setPositiveButton(getString(R.string.yesAnswer), dialogClickListener) 90 | builder.setNegativeButton(getString(R.string.noAnswer), dialogClickListener) 91 | dialog = builder.create() 92 | dialog.show() 93 | } 94 | 95 | private fun goToInvoiceReviewScreen() { 96 | val intent = Intent(this, InvoiceReviewScreen::class.java) 97 | startActivity(intent) 98 | } 99 | 100 | override fun onBackPressed() { 101 | goToInvoiceReviewScreen() 102 | } 103 | } 104 | 105 | fun getAndShowDate(): String { 106 | return Calendar.getInstance().timeInMillis.toString() 107 | 108 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/woodinvoicetest/Common.kt: -------------------------------------------------------------------------------- 1 | package com.example.woodinvoicetest 2 | 3 | import android.content.Context 4 | import android.util.Log 5 | import java.io.File 6 | 7 | object Common { 8 | fun getAppPath(context: Context): String { 9 | val dir = File( 10 | android.os.Environment.getExternalStorageDirectory().toString() + 11 | File.separator + "invoices" + File.separator 12 | ) 13 | if (!dir.exists()) { 14 | dir.mkdirs() 15 | } 16 | return dir.path + File.separator 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/woodinvoicetest/ConfirmEverything.kt: -------------------------------------------------------------------------------- 1 | package com.example.woodinvoicetest 2 | 3 | import android.content.Intent 4 | import androidx.appcompat.app.AppCompatActivity 5 | import android.os.Bundle 6 | import android.util.Log 7 | import android.widget.Toast 8 | import androidx.databinding.DataBindingUtil 9 | import androidx.lifecycle.ViewModelProviders 10 | import com.android.volley.DefaultRetryPolicy 11 | import com.android.volley.Request 12 | import com.android.volley.Response 13 | import com.android.volley.toolbox.StringRequest 14 | import com.android.volley.toolbox.Volley 15 | import com.itextpdf.text.* 16 | import com.itextpdf.text.pdf.PdfWriter 17 | import com.karumi.dexter.Dexter 18 | import com.karumi.dexter.PermissionToken 19 | import com.karumi.dexter.listener.PermissionRequest 20 | import kotlinx.android.synthetic.main.activity_confirm_everything.* 21 | import java.io.File 22 | import java.io.FileOutputStream 23 | import com.example.woodinvoicetest.databinding.ActivityConfirmEverythingBinding 24 | import com.google.gson.Gson 25 | import com.karumi.dexter.MultiplePermissionsReport 26 | import com.karumi.dexter.listener.multi.MultiplePermissionsListener 27 | import java.lang.Exception 28 | 29 | class ConfirmEverything : AppCompatActivity() { 30 | private val filename: String = "invoice.pdf" 31 | 32 | override fun onCreate(savedInstanceState: Bundle?) { 33 | super.onCreate(savedInstanceState) 34 | val binding: ActivityConfirmEverythingBinding = 35 | DataBindingUtil.setContentView(this, R.layout.activity_confirm_everything) 36 | 37 | Toast.makeText(this, getString(R.string.messageConfirmPayment), Toast.LENGTH_LONG).show() 38 | 39 | val application = requireNotNull(this).application 40 | val dataSource = WoodInvoicesDatabase.getInstance(application).woodInvoiceDao 41 | val woodInvoicesViewModelFactory = WoodInvoicesViewModelFactory(dataSource, application) 42 | val woodInvoicesViewModel = ViewModelProviders.of(this, woodInvoicesViewModelFactory) 43 | .get(WoodInvoicesViewModel::class.java) 44 | 45 | showInvoice.setOnClickListener { goToShowOneInvoice() } 46 | backToMainScreen.setOnClickListener { finishInvoice() } 47 | 48 | Dexter.withContext(this) 49 | .withPermissions( 50 | android.Manifest.permission.WRITE_EXTERNAL_STORAGE, 51 | android.Manifest.permission.INTERNET 52 | ) 53 | .withListener(object : MultiplePermissionsListener { 54 | override fun onPermissionsChecked(p0: MultiplePermissionsReport?) { 55 | printInvoiceButton.setOnClickListener { sendInvoiceToPrinter() } 56 | if (ordersInvoice.sendToServer == "false") { 57 | val jsonOrderedInvoice = dataClassToJson() 58 | sendInvoiceToServer(jsonOrderedInvoice) 59 | ordersInvoice.sendToServer = "true" 60 | } 61 | } 62 | 63 | override fun onPermissionRationaleShouldBeShown( 64 | p0: MutableList?, 65 | p1: PermissionToken? 66 | ) { 67 | doNothing() 68 | } 69 | }) 70 | .check() 71 | 72 | binding.lifecycleOwner = this 73 | binding.woodInvoicesViewModel = woodInvoicesViewModel 74 | if (ordersInvoice.savedToDatabase == "false") { 75 | woodInvoicesViewModel.onConfirmEverything() 76 | ordersInvoice.savedToDatabase = "true" 77 | } 78 | 79 | } 80 | 81 | 82 | fun sendInvoiceToPrinter() { 83 | val pdfPath = Common.getAppPath(this@ConfirmEverything) + filename 84 | createPDFFile(pdfPath) 85 | // Toast.makeText(this, "the pdf path is $pdfPath", Toast.LENGTH_SHORT).show() 86 | Toast.makeText(this, "This feature is in development", Toast.LENGTH_LONG).show() 87 | } 88 | 89 | override fun onBackPressed() { 90 | finishInvoice() 91 | } 92 | 93 | private fun finishInvoice() { 94 | ordersInvoice = InvoiceObjectClass() 95 | orderedProduct = OrderedProduct() 96 | val intent = Intent(this, MainActivity::class.java) 97 | startActivity(intent) 98 | } 99 | 100 | private fun createPDFFile(path: String) { 101 | if (File(path).exists()) { 102 | File(path).delete() 103 | print("pdf deleted") 104 | } 105 | val document = Document() 106 | PdfWriter.getInstance(document, FileOutputStream(path)) 107 | document.open() 108 | document.pageSize = PageSize.A4 109 | document.addCreationDate() 110 | 111 | val colorAccent = BaseColor(0, 153, 204, 255) 112 | val headingFontSize = 20.0f 113 | val valueFontSize = 26.0f 114 | 115 | val titleStyle = Font(Font.FontFamily.COURIER, 36.0f, Font.NORMAL, BaseColor.BLACK) 116 | addNewItem(document, "Invoice", Element.ALIGN_CENTER, titleStyle) 117 | document.close() 118 | Toast.makeText(this, "the pdf path is $path", Toast.LENGTH_SHORT).show() 119 | Toast.makeText(this, getString(R.string.messageConfirmPrinting), Toast.LENGTH_LONG).show() 120 | 121 | } 122 | 123 | @Throws(DocumentException::class) 124 | private fun addNewItem(document: Document, text: String, align: Int, style: Font) { 125 | val chunk = Chunk(text, style) 126 | val p = Paragraph(chunk) 127 | p.alignment = align 128 | document.add(p) 129 | } 130 | 131 | fun dataClassToJson(): String { 132 | val gson = Gson() 133 | val jsonTut: String = gson.toJson(ordersInvoice) 134 | Log.i("json", "the json object: $jsonTut") 135 | return jsonTut 136 | } 137 | 138 | fun sendInvoiceToServer(jsonOrderedInvoice: String) { 139 | val externalURL = getString(R.string.externalServerUrl) 140 | val internalURL = getString(R.string.internalServerUrl) 141 | val stringRequest: StringRequest = object : StringRequest(Request.Method.POST, internalURL, 142 | Response.Listener { response -> 143 | try { 144 | Log.i( 145 | "server", 146 | "the invoice was sent to the server and the response is $response" 147 | ) 148 | Toast.makeText( 149 | this, 150 | getString(R.string.messageConfirmSentToServer), 151 | Toast.LENGTH_LONG 152 | ) 153 | .show() 154 | } catch (e: Exception) { 155 | Toast.makeText( 156 | this, 157 | getString(R.string.messageErrorSendToServer), 158 | Toast.LENGTH_LONG 159 | ) 160 | .show() 161 | Log.i("server", "did not work $e") 162 | } 163 | }, Response.ErrorListener { 164 | Log.i("server", "Error $it") 165 | }) { 166 | override fun getParams(): Map { 167 | val params: MutableMap = HashMap() 168 | //Change with your post params 169 | params["sender"] = getString(R.string.serverPassword) 170 | params["data"] = jsonOrderedInvoice 171 | return params 172 | } 173 | } 174 | stringRequest.retryPolicy = DefaultRetryPolicy(DefaultRetryPolicy.DEFAULT_TIMEOUT_MS, 0, 1F) 175 | val requestQueue = Volley.newRequestQueue(this) 176 | requestQueue.add(stringRequest) 177 | } 178 | 179 | private fun goToShowOneInvoice() { 180 | val intent = Intent(this, ShowSingleInvoice::class.java) 181 | intent.putExtra("invoiceId", ordersInvoice.invoiceId) 182 | intent.putExtra("invoiceNote", ordersInvoice.invoiceNotes) 183 | intent.putExtra("invoiceSold", ordersInvoice.invoiceSold.toString()) 184 | intent.putExtra( 185 | "netInvoiceValue", 186 | (ordersInvoice.invoiceTotalSum - ordersInvoice.invoiceSold).toString() 187 | ) 188 | startActivity(intent) 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/woodinvoicetest/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.woodinvoicetest 2 | 3 | import android.content.DialogInterface 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import androidx.appcompat.app.AlertDialog 7 | import androidx.appcompat.app.AppCompatActivity 8 | import kotlinx.android.synthetic.main.activity_main.* 9 | import java.text.DateFormat 10 | import java.text.SimpleDateFormat 11 | import java.util.* 12 | 13 | 14 | class MainActivity : AppCompatActivity() { 15 | override fun onCreate(savedInstanceState: Bundle?) { 16 | super.onCreate(savedInstanceState) 17 | setContentView(R.layout.activity_main) 18 | getAndShowDate() 19 | 20 | enterMaterialProductButton.setOnClickListener { 21 | goToWoodProductsScreen() 22 | } 23 | showInvoicesButton.setOnClickListener { goToShowInvoices() } 24 | exitProgramButton.setOnClickListener { exitApp() } 25 | } 26 | 27 | private fun goToWoodProductsScreen() { 28 | val intent = Intent(this, WoodProducts::class.java) 29 | ordersInvoice = InvoiceObjectClass() 30 | orderedProduct = OrderedProduct() 31 | startActivity(intent) 32 | } 33 | 34 | override fun onBackPressed() { 35 | exitApp() 36 | } 37 | 38 | private fun exitApp() { 39 | showYesNoDialog() 40 | } 41 | 42 | private fun showYesNoDialog() { 43 | lateinit var dialog: AlertDialog 44 | val builder = AlertDialog.Builder(this) 45 | builder.setTitle(getString(R.string.finishTheAppTitle)) 46 | builder.setMessage(getString(R.string.messageDoYouWantToFinishTheApp)) 47 | val dialogClickListener = DialogInterface.OnClickListener { _, which -> 48 | when (which) { 49 | DialogInterface.BUTTON_POSITIVE -> this.finishAffinity() 50 | DialogInterface.BUTTON_NEGATIVE -> doNothing() 51 | } 52 | } 53 | builder.setPositiveButton(getString(R.string.yesAnswer), dialogClickListener) 54 | builder.setNegativeButton(getString(R.string.noAnswer), dialogClickListener) 55 | dialog = builder.create() 56 | dialog.show() 57 | } 58 | 59 | private fun goToShowInvoices() { 60 | val intent = Intent(this, ShowInvoices::class.java) 61 | startActivity(intent) 62 | } 63 | 64 | private fun getAndShowDate() { 65 | val calender = Calendar.getInstance().time 66 | val formatter = SimpleDateFormat.getDateInstance(DateFormat.MEDIUM, Locale.GERMAN) 67 | actualDate.text = formatter.format(calender) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/woodinvoicetest/QuantityAndPrice.kt: -------------------------------------------------------------------------------- 1 | package com.example.woodinvoicetest 2 | 3 | import android.content.DialogInterface 4 | import android.content.Intent 5 | import androidx.appcompat.app.AppCompatActivity 6 | import android.os.Bundle 7 | import android.text.Editable 8 | import android.text.InputType 9 | import android.text.TextWatcher 10 | import android.widget.Toast 11 | import androidx.appcompat.app.AlertDialog 12 | import kotlinx.android.synthetic.main.activity_quantity_and_price.* 13 | import java.lang.Exception 14 | import kotlin.math.round 15 | 16 | class QuantityAndPrice : AppCompatActivity() { 17 | 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | setContentView(R.layout.activity_quantity_and_price) 21 | 22 | totalAmountInput?.setText(orderedProduct.numberOfUnits.toString()) 23 | unitPriceInput?.setText(orderedProduct.unitPrice.toString()) 24 | 25 | if (orderedProduct.productLength * orderedProduct.productThickness * orderedProduct.productWidth < 1e-5 && 26 | orderedProduct.productType == ProductType.Sawnwood.toString() 27 | ) { 28 | totalAmountLabel.text = getString(R.string.totalVolumeText) 29 | totalAmountInput.inputType = unitPriceInput.inputType 30 | } else { 31 | totalAmountLabel.text = getString(R.string.totalNumberText) 32 | totalAmountInput.inputType = InputType.TYPE_CLASS_NUMBER 33 | } 34 | 35 | totalAmountInput.addTextChangedListener(object : TextWatcher { 36 | override fun afterTextChanged(s: Editable) {} 37 | override fun beforeTextChanged( 38 | s: CharSequence, start: Int, 39 | count: Int, after: Int 40 | ) { 41 | } 42 | 43 | override fun onTextChanged( 44 | s: CharSequence, start: Int, 45 | before: Int, count: Int 46 | ) { 47 | val totalVolume = round(calculateVolume() * 10e+5) / 10e+5 48 | totalVolumeoutput.text = totalVolume.toString() 49 | 50 | if (unitPriceInput.text.isNotEmpty()) { 51 | val totalPrice = round(calculateTotalPrice() * 100) / 100 52 | totalPriceOutput.text = decimalFormatChanger.format(totalPrice).toString() 53 | } 54 | } 55 | }) 56 | 57 | 58 | unitPriceInput.addTextChangedListener(object : TextWatcher { 59 | override fun afterTextChanged(s: Editable) {} 60 | override fun beforeTextChanged( 61 | s: CharSequence, start: Int, 62 | count: Int, after: Int 63 | ) { 64 | } 65 | 66 | override fun onTextChanged( 67 | s: CharSequence, start: Int, 68 | before: Int, count: Int 69 | ) { 70 | if (s.isNotEmpty()) { 71 | val totalPrice = round(calculateTotalPrice() * 100) / 100 72 | totalPriceOutput.text = decimalFormatChanger.format(totalPrice).toString() 73 | } 74 | } 75 | }) 76 | backButton.setOnClickListener { goToWoodProductsScreen() } 77 | addToInvoiceButton.setOnClickListener { goToInvoiceReviewScreen() } 78 | cancelInvoiceButton.setOnClickListener { resetInvoiceYesNoDialog() } 79 | } 80 | 81 | private fun goToInvoiceReviewScreen() { 82 | if (unitPriceInput.text.isNotEmpty() && totalAmountInput.text.isNotEmpty()) { 83 | assignDataToOrderedProduct() 84 | ordersInvoice.orderedProductList.add(orderedProduct) 85 | val intent = Intent(this, InvoiceReviewScreen::class.java) 86 | startActivity(intent) 87 | } else { 88 | Toast.makeText( 89 | this, 90 | getString(R.string.errorMessageNoAmountAndUnitPrice), 91 | Toast.LENGTH_SHORT 92 | ).show() 93 | } 94 | 95 | } 96 | 97 | private fun assignDataToOrderedProduct() { 98 | assignValuesToOrderedProduct() 99 | var totalPrice = 0.0 100 | if (orderedProduct.productType == ProductType.Sawnwood.toString()) { 101 | val totalVolume = calculateVolume() 102 | orderedProduct.totalVolume = round(totalVolume * 10e+5) / 10e+5 103 | totalPrice = orderedProduct.unitPrice * totalVolume 104 | } 105 | if (orderedProduct.productType == ProductType.Plywood.toString()) { 106 | totalPrice = orderedProduct.unitPrice * orderedProduct.numberOfUnits 107 | } 108 | orderedProduct.totalPrice = round(totalPrice * 100) / 100 109 | } 110 | 111 | fun calculateVolume(): Double { 112 | val totalAmount = try { 113 | totalAmountInput.text.toString().toInt() 114 | } catch (e: Exception) { 115 | 0 116 | } 117 | 118 | var totalVolume = 0.0 119 | if (orderedProduct.productType == ProductType.Sawnwood.toString()) { 120 | totalVolume = 121 | totalAmount * orderedProduct.productLength * orderedProduct.productWidth * orderedProduct.productThickness 122 | } 123 | 124 | if (orderedProduct.productLength * orderedProduct.productWidth * orderedProduct.productThickness < 1e-5 && 125 | orderedProduct.productType == ProductType.Sawnwood.toString() 126 | ) { 127 | totalVolume = try { 128 | totalAmountInput.text.toString().toDouble() 129 | } catch (e: Exception) { 130 | 0.0 131 | } 132 | 133 | } 134 | return totalVolume 135 | } 136 | 137 | fun calculateTotalPrice(): Double { 138 | var totalPrice = 0.0 139 | assignValuesToOrderedProduct() 140 | 141 | if (orderedProduct.productType == ProductType.Sawnwood.toString()) { 142 | val totalVolume = calculateVolume() 143 | orderedProduct.totalVolume = totalVolume 144 | totalPrice = orderedProduct.unitPrice * totalVolume 145 | } 146 | if (orderedProduct.productType == ProductType.Plywood.toString()) { 147 | totalPrice = orderedProduct.unitPrice * orderedProduct.numberOfUnits 148 | } 149 | return totalPrice 150 | } 151 | 152 | private fun assignValuesToOrderedProduct() { 153 | try { 154 | orderedProduct.unitPrice = unitPriceInput.text.toString().toDouble() 155 | } catch (e: Exception) { 156 | orderedProduct.unitPrice = 0.0 157 | } 158 | 159 | try { 160 | orderedProduct.numberOfUnits = totalAmountInput.text.toString().toInt() 161 | } catch (e: Exception) { 162 | orderedProduct.numberOfUnits = 0 163 | } 164 | } 165 | 166 | override fun onBackPressed() { 167 | goToWoodProductsScreen() 168 | } 169 | 170 | private fun goToWoodProductsScreen() { 171 | val intent = Intent(this, WoodProducts::class.java) 172 | startActivity(intent) 173 | } 174 | 175 | private fun cancelInvoice() { 176 | ordersInvoice = InvoiceObjectClass() 177 | orderedProduct = OrderedProduct() 178 | val intent = Intent(this, MainActivity::class.java) 179 | startActivity(intent) 180 | } 181 | 182 | private fun resetInvoiceYesNoDialog() { 183 | lateinit var dialog: AlertDialog 184 | val builder = AlertDialog.Builder(this) 185 | builder.setTitle(getString(R.string.cancelInvoiceMessageTitle)) 186 | builder.setMessage(getString(R.string.doYouWantToCancelInvoiceMessage)) 187 | val dialogClickListener = DialogInterface.OnClickListener { _, which -> 188 | when (which) { 189 | DialogInterface.BUTTON_POSITIVE -> cancelInvoice() 190 | DialogInterface.BUTTON_NEGATIVE -> doNothing() 191 | } 192 | } 193 | builder.setPositiveButton(getString(R.string.yesAnswer), dialogClickListener) 194 | builder.setNegativeButton(getString(R.string.noAnswer), dialogClickListener) 195 | dialog = builder.create() 196 | dialog.show() 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/woodinvoicetest/SendToServer.kt: -------------------------------------------------------------------------------- 1 | package com.example.woodinvoicetest 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import android.os.Bundle 5 | import android.util.Log 6 | import android.widget.Toast 7 | import androidx.databinding.DataBindingUtil 8 | import androidx.lifecycle.Observer 9 | import androidx.lifecycle.ViewModelProviders 10 | import com.android.volley.DefaultRetryPolicy 11 | import com.android.volley.Request 12 | import com.android.volley.Response 13 | import com.android.volley.toolbox.StringRequest 14 | import com.android.volley.toolbox.Volley 15 | import com.example.woodinvoicetest.databinding.ActivitySendToServerBinding 16 | import com.google.gson.Gson 17 | import kotlinx.android.synthetic.main.activity_send_to_server.* 18 | import java.lang.Exception 19 | 20 | class SendToServer : AppCompatActivity() { 21 | 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | super.onCreate(savedInstanceState) 24 | val binding: ActivitySendToServerBinding = 25 | DataBindingUtil.setContentView(this, R.layout.activity_send_to_server) 26 | 27 | val application = requireNotNull(this).application 28 | val dataSource = WoodInvoicesDatabase.getInstance(application).woodInvoiceDao 29 | val woodInvoicesViewModelFactory = WoodInvoicesViewModelFactory(dataSource, application) 30 | val woodInvoicesViewModel = ViewModelProviders.of(this, woodInvoicesViewModelFactory) 31 | .get(WoodInvoicesViewModel::class.java) 32 | binding.lifecycleOwner = this 33 | binding.woodInvoicesViewModel = woodInvoicesViewModel 34 | 35 | val invoicesList = intent.getStringArrayListExtra("invoicesList") 36 | 37 | val invoicesMap: MutableMap = HashMap() 38 | val productsMap: MutableMap> = HashMap() 39 | 40 | if (invoicesList != null) { 41 | for (invoiceId in invoicesList) { 42 | val myLiveDAtaObserver = Observer { invoiceById -> 43 | 44 | val toSendInvoice = InvoiceObjectClass( 45 | invoiceId = invoiceById.invoiceId, 46 | customerName = invoiceById.customerName, 47 | invoiceTotalSum = invoiceById.totalSum, 48 | invoiceSold = invoiceById.invoiceSold, 49 | invoiceNotes = invoiceById.invoiceNotes 50 | ) 51 | invoicesMap[invoiceById.invoiceId] = toSendInvoice 52 | } 53 | woodInvoicesViewModel.getInvoicesById(invoiceId).observe(this, myLiveDAtaObserver) 54 | } 55 | 56 | for (invoiceId in invoicesList) { 57 | val productsObserver = Observer> { productsList -> 58 | val productsListGathers: ArrayList = ArrayList() 59 | for (orderedProductI in productsList.indices) { 60 | val productI = productsList[orderedProductI] 61 | val toSendProduct = OrderedProduct( 62 | productId = productI.productId, 63 | productName = productI.productName, 64 | productType = productI.productType, 65 | marketProductName = productI.marketProductName, 66 | productThickness = productI.productThickness, 67 | productWidth = productI.productWidth, 68 | productLength = productI.productLength, 69 | productProperty1 = productI.productProperty1, 70 | productProperty2 = productI.productProperty2, 71 | productProperty3 = productI.productProperty3, 72 | numberOfUnits = productI.numberOfUnits, 73 | totalVolume = productI.totalVolume, 74 | unitPrice = productI.unitPrice, 75 | totalPrice = productI.totalPrice 76 | ) 77 | productsListGathers.add(toSendProduct) 78 | productsMap[productI.invoiceId] = productsListGathers 79 | } 80 | } 81 | woodInvoicesViewModel.getProducts(invoiceId).observe(this, productsObserver) 82 | } 83 | sendButton.setOnClickListener { 84 | var counterI = 0 85 | for (invoiceId in invoicesList) { 86 | if (productsMap[invoiceId] != null) { 87 | for (orderedProductI in productsMap[invoiceId]!!) { 88 | invoicesMap[invoiceId]?.orderedProductList?.add(orderedProductI) 89 | } 90 | } 91 | val jsonOrderedInvoice = invoicesMap[invoiceId]?.let { it1 -> 92 | dataClassToJson( 93 | it1 94 | ) 95 | } 96 | if (jsonOrderedInvoice != null) { 97 | sendInvoiceToServer(jsonOrderedInvoice) 98 | } 99 | counterI += 1 100 | determinateBar.progress = 100 * counterI / invoicesList.size 101 | } 102 | } 103 | } 104 | } 105 | 106 | private fun dataClassToJson(toSendInvoice: InvoiceObjectClass): String { 107 | val gson = Gson() 108 | return gson.toJson(toSendInvoice) 109 | } 110 | 111 | private fun sendInvoiceToServer(jsonOrderedInvoice: String) { 112 | val externalURL = getString(R.string.externalServerUrl) 113 | val internalURL = getString(R.string.internalServerUrl) 114 | val stringRequest: StringRequest = object : StringRequest(Request.Method.POST, internalURL, 115 | Response.Listener { response -> 116 | try { 117 | Log.i( 118 | "server", 119 | "the invoice was sent to the server and the response is $response" 120 | ) 121 | Toast.makeText( 122 | this, 123 | getString(R.string.messageConfirmSentToServer), 124 | Toast.LENGTH_LONG 125 | ) 126 | .show() 127 | } catch (e: Exception) { 128 | Toast.makeText( 129 | this, 130 | getString(R.string.messageErrorSendToServer), 131 | Toast.LENGTH_LONG 132 | ) 133 | .show() 134 | Log.i("server", "did not work $e") 135 | } 136 | }, Response.ErrorListener { 137 | Log.i("server", "Error $it") 138 | }) { 139 | override fun getParams(): Map { 140 | val params: MutableMap = HashMap() 141 | //Change with your post params 142 | params["sender"] = getString(R.string.serverPassword) 143 | params["data"] = jsonOrderedInvoice 144 | return params 145 | } 146 | } 147 | stringRequest.retryPolicy = DefaultRetryPolicy(DefaultRetryPolicy.DEFAULT_TIMEOUT_MS, 0, 1F) 148 | val requestQueue = Volley.newRequestQueue(this) 149 | requestQueue.add(stringRequest) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/woodinvoicetest/ShowInvoices.kt: -------------------------------------------------------------------------------- 1 | package com.example.woodinvoicetest 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.DatePickerDialog 5 | import android.content.Intent 6 | import android.os.Bundle 7 | import android.util.Log 8 | import android.widget.LinearLayout 9 | import android.widget.TableRow 10 | import android.widget.TextView 11 | import android.widget.Toast 12 | import androidx.appcompat.app.AppCompatActivity 13 | import androidx.databinding.DataBindingUtil 14 | import androidx.lifecycle.Observer 15 | import androidx.lifecycle.ViewModelProviders 16 | import com.example.woodinvoicetest.databinding.ActivityShowInvoicesBinding 17 | import kotlinx.android.synthetic.main.activity_invoice_review_screen.invoiceTable 18 | import kotlinx.android.synthetic.main.activity_show_invoices.* 19 | import java.text.SimpleDateFormat 20 | import java.util.* 21 | import kotlin.collections.ArrayList 22 | 23 | 24 | class ShowInvoices : AppCompatActivity() { 25 | var rowsList: ArrayList = ArrayList() 26 | val formatter = SimpleDateFormat("dd.MM.yyyy") 27 | var invoicesSum: Double = 0.0 28 | 29 | override fun onCreate(savedInstanceState: Bundle?) { 30 | super.onCreate(savedInstanceState) 31 | val binding: ActivityShowInvoicesBinding = 32 | DataBindingUtil.setContentView(this, R.layout.activity_show_invoices) 33 | 34 | showColumnsTitles() 35 | initiateDate() 36 | 37 | val application = requireNotNull(this).application 38 | val dataSource = WoodInvoicesDatabase.getInstance(application).woodInvoiceDao 39 | val woodInvoicesViewModelFactory = WoodInvoicesViewModelFactory(dataSource, application) 40 | val woodInvoicesViewModel = ViewModelProviders.of(this, woodInvoicesViewModelFactory) 41 | .get(WoodInvoicesViewModel::class.java) 42 | binding.lifecycleOwner = this 43 | binding.woodInvoicesViewModel = woodInvoicesViewModel 44 | val myLiveDAtaObserver = Observer> { InvoiceList -> 45 | val invoicesList: ArrayList = ArrayList() 46 | var counterI = 0 47 | invoicesSum = 0.0 48 | for (invoiceI in InvoiceList) { 49 | 50 | val row = TableRow(this) 51 | row.gravity = 1 52 | row.layoutParams = LinearLayout.LayoutParams( 53 | LinearLayout.LayoutParams.MATCH_PARENT, 54 | LinearLayout.LayoutParams.WRAP_CONTENT 55 | ) 56 | val netInvoiceValue = invoiceI.totalSum - invoiceI.invoiceSold 57 | 58 | var invoiceText = counterI.toString() 59 | createTextView(row, invoiceText) 60 | invoiceText = invoiceI.customerName 61 | createTextView(row, invoiceText) 62 | invoiceText = invoiceI.nameOfProducts 63 | createTextView(row, invoiceText) 64 | invoiceText = 65 | """${decimalFormatChanger.format(netInvoiceValue)} ${getString(R.string.currencyUnitText)} """ 66 | createTextView(row, invoiceText) 67 | 68 | invoicesSum += netInvoiceValue 69 | counterI += 1 70 | invoiceTable.addView(row) 71 | rowsList.add(row) 72 | invoicesList.add(invoiceI.invoiceId) 73 | } 74 | invoicesSumTextView.text = decimalFormatChanger.format(invoicesSum).toString() 75 | for (i in rowsList.indices) { 76 | rowsList[i].setOnClickListener { showInvoice(InvoiceList[i], i) } 77 | } 78 | sendInvoicestoServer.setOnClickListener { goToSendToServer(invoicesList) } 79 | 80 | } 81 | startChosenDateTextView.setOnClickListener { chooseDatesDialog(startChosenDateTextView) } 82 | endChosenDateTextView.setOnClickListener { chooseDatesDialog(endChosenDateTextView) } 83 | 84 | showInvoiceButton.setOnClickListener { 85 | showInvoicesFunction( 86 | woodInvoicesViewModel, 87 | myLiveDAtaObserver 88 | ) 89 | } 90 | } 91 | 92 | fun goToSendToServer(invoicesList: ArrayList) { 93 | val intent = Intent(this, SendToServer::class.java) 94 | intent.putStringArrayListExtra("invoicesList", invoicesList) 95 | startActivity(intent) 96 | } 97 | 98 | private fun showInvoicesFunction( 99 | woodInvoicesViewModel: WoodInvoicesViewModel, 100 | myLiveDAtaObserver: Observer> 101 | ) { 102 | val startChosenDate = formatter.parse(startChosenDateTextView.text.toString()).time 103 | val endChosenDate = formatter.parse(endChosenDateTextView.text.toString()).time 104 | 105 | if (rowsList.isNotEmpty()) { 106 | invoiceTable.removeAllViews() 107 | showColumnsTitles() 108 | } 109 | 110 | 111 | woodInvoicesViewModel.getInvoices(startChosenDate, endChosenDate) 112 | .observe(this, myLiveDAtaObserver) 113 | } 114 | 115 | private fun showInvoice(invoice: InvoicesTable, invoiceNumber: Int) { 116 | Toast.makeText(this, "Currency Nr. $invoiceNumber", Toast.LENGTH_SHORT).show() 117 | val netInvoiceValue = invoice.totalSum - invoice.invoiceSold 118 | goToShowOneInvoice( 119 | invoice.invoiceId, 120 | invoice.invoiceNotes, 121 | netInvoiceValue, 122 | invoice.invoiceSold 123 | ) 124 | } 125 | 126 | private fun createTableColumnsTitles(row: TableRow) { 127 | 128 | var invoiceText = " ${getString(R.string.idText)} " 129 | createTextView(row, invoiceText) 130 | invoiceText = " ${getString(R.string.customerName)} " 131 | createTextView(row, invoiceText) 132 | invoiceText = getString(R.string.Products) 133 | createTextView(row, invoiceText) 134 | invoiceText = " ${getString(R.string.totalPriceText)} " 135 | createTextView(row, invoiceText) 136 | } 137 | 138 | fun showColumnsTitles() { 139 | val firstRow = TableRow(this) 140 | createTableColumnsTitles(firstRow) 141 | invoiceTable.addView(firstRow) 142 | } 143 | 144 | fun createTextView(row: TableRow, invoiceText: String) { 145 | val textView: TextView = TextView(this) 146 | textView.apply { 147 | layoutParams = TableRow.LayoutParams( 148 | TableRow.LayoutParams.WRAP_CONTENT, 149 | TableRow.LayoutParams.WRAP_CONTENT 150 | ) 151 | 152 | text = invoiceText 153 | gravity = 1 154 | textSize = 16F 155 | 156 | } 157 | textView.margin(0F, 5F, 0F, 5F) 158 | 159 | row.addView(textView) 160 | } 161 | 162 | fun goToShowOneInvoice( 163 | invoiceId: String, invoiceNotes: String, 164 | netInvoiceValue: Double, invoiceSold: Double 165 | ) { 166 | val intent = Intent(this, ShowSingleInvoice::class.java) 167 | intent.putExtra("invoiceId", invoiceId) 168 | intent.putExtra("invoiceNote", invoiceNotes) 169 | intent.putExtra("invoiceSold", invoiceSold.toString()) 170 | intent.putExtra("netInvoiceValue", netInvoiceValue.toString()) 171 | startActivity(intent) 172 | } 173 | 174 | @SuppressLint("SetTextI18n") 175 | fun chooseDatesDialog(vText: TextView) { 176 | val calender = Calendar.getInstance() 177 | val currentYear = calender.get(Calendar.YEAR) 178 | val currentMonth = calender.get(Calendar.MONTH) 179 | val currentDay = calender.get(Calendar.DAY_OF_MONTH) 180 | val dpd = DatePickerDialog( 181 | this, 182 | DatePickerDialog.OnDateSetListener { view, year, monthOfYear, dayOfMonth -> 183 | val correctedMonth = monthOfYear + 1 184 | vText.text = 185 | formatter.format(formatter.parse("$dayOfMonth.$correctedMonth.$year")) 186 | .toString() 187 | }, 188 | currentYear, 189 | currentMonth, 190 | currentDay 191 | ) 192 | dpd.show() 193 | } 194 | 195 | fun initiateDate() { 196 | val calender = Calendar.getInstance() 197 | val currentYear = calender.get(Calendar.YEAR) 198 | var currentMonth = calender.get(Calendar.MONTH) 199 | var currentDay = calender.get(Calendar.DAY_OF_MONTH) 200 | currentMonth += 1 201 | startChosenDateTextView.text = 202 | formatter.format(formatter.parse("$currentDay.$currentMonth.$currentYear")) 203 | currentDay += 1 204 | endChosenDateTextView.text = 205 | formatter.format(formatter.parse("$currentDay.$currentMonth.$currentYear")) 206 | 207 | } 208 | 209 | } 210 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/woodinvoicetest/ShowSingleInvoice.kt: -------------------------------------------------------------------------------- 1 | package com.example.woodinvoicetest 2 | 3 | import android.annotation.SuppressLint 4 | import androidx.appcompat.app.AppCompatActivity 5 | import android.os.Bundle 6 | import android.widget.TableRow 7 | import android.widget.TextView 8 | import androidx.databinding.DataBindingUtil 9 | import androidx.lifecycle.Observer 10 | import androidx.lifecycle.ViewModelProviders 11 | import com.example.woodinvoicetest.databinding.ActivityShowSingleInvoiceBinding 12 | import kotlinx.android.synthetic.main.activity_show_single_invoice.* 13 | 14 | class ShowSingleInvoice : AppCompatActivity() { 15 | 16 | @SuppressLint("SetTextI18n") 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | val binding: ActivityShowSingleInvoiceBinding = 20 | DataBindingUtil.setContentView(this, R.layout.activity_show_single_invoice) 21 | val invoiceNumber = intent.getStringExtra("invoiceId") 22 | val invoiceNotes = intent.getStringExtra("invoiceNote") 23 | val invoiceSold = intent.getStringExtra("invoiceSold") 24 | val netInvoiceValue = intent.getStringExtra("netInvoiceValue") 25 | 26 | val application = requireNotNull(this).application 27 | val dataSource = WoodInvoicesDatabase.getInstance(application).woodInvoiceDao 28 | val woodInvoicesViewModelFactory = WoodInvoicesViewModelFactory(dataSource, application) 29 | val woodInvoicesViewModel = ViewModelProviders.of(this, woodInvoicesViewModelFactory) 30 | .get(WoodInvoicesViewModel::class.java) 31 | binding.lifecycleOwner = this 32 | binding.woodInvoicesViewModel = woodInvoicesViewModel 33 | 34 | var invoiceTotalSum = 0.0 35 | val firstRow = TableRow(this) 36 | createTableColumnsTitles(firstRow) 37 | invoiceTable.addView(firstRow) 38 | 39 | val myLiveDAtaObserver = Observer> { productsList -> 40 | 41 | for (orderedProductI in productsList.indices) { 42 | val productI = productsList[orderedProductI] 43 | val row = TableRow(this) 44 | val productNumber = orderedProductI + 1 45 | 46 | showOneProduct(productI, row, productNumber) 47 | invoiceTable.addView(row) 48 | invoiceTotalSum += productI.totalPrice 49 | } 50 | totalPrice.text = decimalFormatChanger.format(invoiceTotalSum).toString() 51 | } 52 | if (invoiceNumber != null) { 53 | woodInvoicesViewModel.getProducts(invoiceNumber).observe(this, myLiveDAtaObserver) 54 | invoiceNumberTextview.text = 55 | """${getString(R.string.invoiceNumberPlaceHolder)} $invoiceNumber""" 56 | 57 | invoiceNoteText.text = invoiceNotes 58 | soldValueText.text = invoiceSold 59 | netValueText.text = decimalFormatChanger.format(netInvoiceValue.toDouble()).toString() 60 | } 61 | 62 | } 63 | 64 | private fun createTableColumnsTitles(row: TableRow) { 65 | 66 | var invoiceText = getString(R.string.idText) 67 | createTextView(row, invoiceText) 68 | invoiceText = getString(R.string.propertiesLabelText) 69 | createTextView(row, invoiceText) 70 | invoiceText = getString(R.string.totalNumberShortText) 71 | createTextView(row, invoiceText) 72 | invoiceText = getString(R.string.totalVolumeShortText) 73 | createTextView(row, invoiceText) 74 | invoiceText = getString(R.string.unitPriceText) 75 | createTextView(row, invoiceText) 76 | invoiceText = getString(R.string.totalPriceText) 77 | createTextView(row, invoiceText) 78 | } 79 | 80 | private fun createTextView(row: TableRow, invoiceText: String) { 81 | val textView: TextView = TextView(this) 82 | textView.apply { 83 | layoutParams = TableRow.LayoutParams( 84 | TableRow.LayoutParams.WRAP_CONTENT, 85 | TableRow.LayoutParams.WRAP_CONTENT 86 | ) 87 | text = invoiceText 88 | textSize = 16F 89 | gravity = 1 90 | } 91 | textView.margin(2F, 2F, 2F, 2F) 92 | row.addView(textView) 93 | } 94 | 95 | private fun showOneProduct(productI: ProductsTable, row: TableRow, productNumber: Int) { 96 | var invoiceText = if (productNumber < 10) { 97 | " 00$productNumber " 98 | } else { 99 | " 0$productNumber " 100 | } 101 | createTextView(row, invoiceText) 102 | invoiceText = productI.marketProductName 103 | 104 | if (productI.productWidth > 0.0) { 105 | invoiceText += """ ${getString(R.string.widthText)} ${(productI.productWidth * 100)} ${getString( 106 | R.string.cm 107 | )} """ 108 | } 109 | 110 | if (productI.productThickness > 0.0) { 111 | invoiceText += """ ${getString(R.string.thicknessText)} ${(productI.productThickness * 1000)} ${getString( 112 | R.string.mm 113 | )} """ 114 | } 115 | 116 | if (productI.productLength > 0.0) { 117 | invoiceText += """ ${getString(R.string.lengthText)} ${productI.productLength} ${getString( 118 | R.string.m 119 | )} """ 120 | } 121 | 122 | if (productI.productProperty1.isNotEmpty()) { 123 | invoiceText += """ ${productI.productProperty1} """ 124 | } 125 | if (productI.productProperty2.isNotEmpty()) { 126 | invoiceText += """ ${productI.productProperty2} """ 127 | } 128 | if (productI.productProperty3.isNotEmpty()) { 129 | invoiceText += """ ${productI.productProperty3} """ 130 | } 131 | createTextView(row, invoiceText) 132 | invoiceText = if (productI.numberOfUnits > 0) { 133 | """ ${productI.numberOfUnits} """ 134 | } else { 135 | """ ${getString(R.string.notGivenText)} """ 136 | } 137 | createTextView(row, invoiceText) 138 | 139 | if (productI.productType == ProductType.Sawnwood.toString()) { 140 | invoiceText = """${productI.totalVolume} ${getString(R.string.m3Unit)} """ 141 | createTextView(row, invoiceText) 142 | } 143 | if (productI.productType == ProductType.Plywood.toString()) { 144 | invoiceText = """ ${getString(R.string.notGivenText)} """ 145 | createTextView(row, invoiceText) 146 | } 147 | invoiceText = 148 | """${decimalFormatChanger.format(productI.unitPrice)} ${getString(R.string.currencyUnitText)} """ 149 | createTextView(row, invoiceText) 150 | invoiceText = 151 | """${decimalFormatChanger.format(productI.totalPrice)} ${getString(R.string.currencyUnitText)} """ 152 | createTextView(row, invoiceText) 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/woodinvoicetest/WoodInvoicesDao.kt: -------------------------------------------------------------------------------- 1 | package com.example.woodinvoicetest 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.room.Dao 5 | import androidx.room.Insert 6 | import androidx.room.Query 7 | import java.util.* 8 | 9 | @Dao 10 | interface WoodInvoicesDao { 11 | @Insert 12 | fun insert(invoice: InvoicesTable) 13 | 14 | @Insert 15 | fun insertProduct(product: ProductsTable) 16 | 17 | @Query("SELECT * FROM invoices_table WHERE Not deleted AND invoiceDate>=:startChosenDate AND invoiceDate<=:endChosenDate") 18 | fun getAllInvoices(startChosenDate: Long, endChosenDate: Long): LiveData> 19 | 20 | @Query("SELECT * FROM invoices_table WHERE Not deleted AND InvoiceId = :invoiceId") 21 | fun getAllInvoicesById(invoiceId: String): LiveData 22 | 23 | @Query("SELECT * FROM products_table WHERE invoiceId=:invoiceNumber") 24 | fun getProducts(invoiceNumber: String): LiveData> 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/woodinvoicetest/WoodInvoicesDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.example.woodinvoicetest 2 | 3 | import android.content.Context 4 | import androidx.room.Database 5 | import androidx.room.Room 6 | import androidx.room.RoomDatabase 7 | 8 | @Database(entities = [InvoicesTable::class, ProductsTable::class], version = 6, exportSchema = false) 9 | abstract class WoodInvoicesDatabase : RoomDatabase() { 10 | abstract val woodInvoiceDao: WoodInvoicesDao 11 | 12 | companion object { 13 | 14 | @Volatile 15 | private var INSTANCE: WoodInvoicesDatabase? = null 16 | 17 | fun getInstance(context: Context): WoodInvoicesDatabase { 18 | synchronized(this) { 19 | var instance = INSTANCE 20 | 21 | if (instance == null) { 22 | instance = Room.databaseBuilder( 23 | context.applicationContext, 24 | WoodInvoicesDatabase::class.java, 25 | "wood_invoices_database" 26 | ) 27 | .fallbackToDestructiveMigration() 28 | .build() 29 | INSTANCE = instance 30 | } 31 | return instance 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/woodinvoicetest/WoodInvoicesRepository.kt: -------------------------------------------------------------------------------- 1 | package com.example.woodinvoicetest 2 | 3 | import androidx.lifecycle.LiveData 4 | 5 | class WoodInvoicesRepository (private val woodInvoicesDao: WoodInvoicesDao) { 6 | 7 | // Room executes all queries on a separate thread. 8 | // Observed LiveData will notify the observer when the data has changed. 9 | val allInvoices: LiveData> = woodInvoicesDao.getAllInvoices(0,0) 10 | 11 | suspend fun insert(invoice: InvoicesTable) { 12 | woodInvoicesDao.insert(invoice) 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/woodinvoicetest/WoodInvoicesViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.example.woodinvoicetest 2 | 3 | import android.app.Application 4 | import android.util.Log 5 | import androidx.lifecycle.AndroidViewModel 6 | import androidx.lifecycle.LiveData 7 | import kotlinx.coroutines.* 8 | 9 | 10 | class WoodInvoicesViewModel( 11 | val database: WoodInvoicesDao, 12 | application: Application 13 | ) : AndroidViewModel(application) { 14 | private val viewModelJob = Job() 15 | 16 | override fun onCleared() { 17 | super.onCleared() 18 | viewModelJob.cancel() 19 | } 20 | 21 | private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob) 22 | fun getInvoices(startChosenDate: Long, endChosenDate: Long): LiveData> { 23 | return database.getAllInvoices(startChosenDate, endChosenDate) 24 | } 25 | 26 | fun getInvoicesById(invoiceID: String): LiveData { 27 | return database.getAllInvoicesById(invoiceID) 28 | } 29 | 30 | fun getProducts(invoiceNumber: String): LiveData> { 31 | return database.getProducts(invoiceNumber) 32 | } 33 | 34 | 35 | fun getNamesOfOrderedProducts(): String { 36 | var nameOfOrderedProducts = "" 37 | for (productI in ordersInvoice.orderedProductList) { 38 | nameOfOrderedProducts += " " + productI.marketProductName + " " 39 | } 40 | return nameOfOrderedProducts 41 | } 42 | 43 | fun onConfirmEverything() { 44 | uiScope.launch { 45 | val nameOfOrderedProducts = getNamesOfOrderedProducts() 46 | val newInvoice = InvoicesTable( 47 | invoiceId = ordersInvoice.invoiceId, 48 | customerName = ordersInvoice.customerName, 49 | nameOfProducts = nameOfOrderedProducts, 50 | totalSum = ordersInvoice.invoiceTotalSum, 51 | invoiceSold = ordersInvoice.invoiceSold, 52 | invoiceNotes = ordersInvoice.invoiceNotes 53 | ) 54 | Log.i("livedata", "insert invoice data: $newInvoice") 55 | insert(newInvoice) 56 | 57 | for (productI in ordersInvoice.orderedProductList) { 58 | val newProduct = ProductsTable( 59 | invoiceId = ordersInvoice.invoiceId, 60 | productId = productI.productId, 61 | productName = productI.productName.toString(), 62 | marketProductName = productI.marketProductName, 63 | productType = productI.productType.toString(), 64 | productThickness = productI.productThickness, 65 | productWidth = productI.productWidth, 66 | productLength = productI.productLength, 67 | productProperty1 = productI.productProperty1, 68 | productProperty2 = productI.productProperty2, 69 | productProperty3 = productI.productProperty3, 70 | numberOfUnits = productI.numberOfUnits, 71 | totalVolume = productI.totalVolume, 72 | unitPrice = productI.unitPrice, 73 | totalPrice = productI.totalPrice 74 | ) 75 | Log.i("livedata", "insert product data: $productI") 76 | insertProduct(newProduct) 77 | } 78 | 79 | } 80 | } 81 | 82 | private suspend fun insert(newInvoice: InvoicesTable) { 83 | withContext(Dispatchers.IO) { 84 | database.insert(newInvoice) 85 | } 86 | } 87 | 88 | private suspend fun insertProduct(newProduct: ProductsTable) { 89 | withContext(Dispatchers.IO) { 90 | database.insertProduct(newProduct) 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/woodinvoicetest/WoodInvoicesViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package com.example.woodinvoicetest 2 | 3 | import android.app.Application 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.ViewModelProvider 6 | import java.lang.IllegalArgumentException 7 | 8 | class WoodInvoicesViewModelFactory( private val dataSource: WoodInvoicesDao, 9 | private val application: Application): ViewModelProvider.Factory { 10 | @Suppress("unchecked_cast") 11 | override fun create(modelClass: Class): T { 12 | if(modelClass.isAssignableFrom(WoodInvoicesViewModel::class.java)){ 13 | return WoodInvoicesViewModel(dataSource, application) as T 14 | } 15 | throw IllegalArgumentException("Unknown ViewModel Class") 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/woodinvoicetest/utils.kt: -------------------------------------------------------------------------------- 1 | package com.example.woodinvoicetest 2 | 3 | import android.content.Context 4 | import android.util.TypedValue 5 | import android.view.View 6 | import android.view.ViewGroup 7 | 8 | fun doNothing(){ 9 | 10 | } 11 | 12 | fun View.margin( 13 | left: Float? = null, 14 | top: Float? = null, 15 | right: Float? = null, 16 | bottom: Float? = null 17 | ) { 18 | layoutParams { 19 | left?.run { leftMargin = dpToPx(this) } 20 | top?.run { topMargin = dpToPx(this) } 21 | right?.run { rightMargin = dpToPx(this) } 22 | bottom?.run { bottomMargin = dpToPx(this) } 23 | } 24 | } 25 | 26 | inline fun View.layoutParams(block: T.() -> Unit) { 27 | if (layoutParams is T) block(layoutParams as T) 28 | } 29 | 30 | fun View.dpToPx(dp: Float): Int = context.dpToPx(dp) 31 | fun Context.dpToPx(dp: Float): Int = 32 | TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.displayMetrics).toInt() -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaherDeeb/take_orders_app/63bdf72166f5a0bcd50d90ea569880aea91d3e83/app/src/main/res/drawable/background.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/button_shape.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 14 | 18 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/choose_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 15 | 19 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/my_cancel_button.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/my_delete_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/my_edit_icon.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/subbackground.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaherDeeb/take_orders_app/63bdf72166f5a0bcd50d90ea569880aea91d3e83/app/src/main/res/drawable/subbackground.jpg -------------------------------------------------------------------------------- /app/src/main/res/layout-land/activity_add_customer_name.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 21 | 22 | 29 | 30 | 39 | 40 | 41 | 53 | 54 |