├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── assets └── sample.gif ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.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-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ └── main.dart ├── linux │ ├── .gitignore │ ├── CMakeLists.txt │ ├── flutter │ │ ├── CMakeLists.txt │ │ ├── generated_plugin_registrant.cc │ │ ├── generated_plugin_registrant.h │ │ └── generated_plugins.cmake │ ├── main.cc │ ├── my_application.cc │ └── my_application.h ├── pubspec.lock ├── pubspec.yaml ├── test │ └── widget_test.dart ├── web │ ├── favicon.png │ ├── icons │ │ ├── Icon-192.png │ │ └── Icon-512.png │ ├── index.html │ └── manifest.json └── windows │ ├── .gitignore │ ├── CMakeLists.txt │ ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake │ └── runner │ ├── CMakeLists.txt │ ├── Runner.rc │ ├── flutter_window.cpp │ ├── flutter_window.h │ ├── main.cpp │ ├── resource.h │ ├── resources │ └── app_icon.ico │ ├── run_loop.cpp │ ├── run_loop.h │ ├── runner.exe.manifest │ ├── utils.cpp │ ├── utils.h │ ├── win32_window.cpp │ └── win32_window.h ├── lib ├── invoiceninja.dart ├── models │ ├── client.dart │ ├── client.freezed.dart │ ├── client.g.dart │ ├── company.dart │ ├── company.freezed.dart │ ├── company.g.dart │ ├── credit.dart │ ├── credit.freezed.dart │ ├── credit.g.dart │ ├── document.dart │ ├── document.freezed.dart │ ├── document.g.dart │ ├── invoice.dart │ ├── invoice.freezed.dart │ ├── invoice.g.dart │ ├── payment.dart │ ├── payment.freezed.dart │ ├── payment.g.dart │ ├── product.dart │ ├── product.freezed.dart │ ├── product.g.dart │ ├── quote.dart │ ├── quote.freezed.dart │ ├── quote.g.dart │ ├── recurring_invoice.dart │ ├── recurring_invoice.freezed.dart │ └── recurring_invoice.g.dart ├── repositories │ ├── client_repository.dart │ ├── company_repository.dart │ ├── credit_repository.dart │ ├── invoice_repository.dart │ ├── payment_repository.dart │ ├── product_repository.dart │ ├── quote_repository.dart │ └── recurring_invoice_repository.dart └── utils │ └── web_client.dart ├── pubspec.lock ├── pubspec.yaml └── test └── invoiceninja_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/app.flx 64 | **/ios/Flutter/app.zip 65 | **/ios/Flutter/flutter_assets/ 66 | **/ios/Flutter/flutter_export_environment.sh 67 | **/ios/ServiceDefinitions.json 68 | **/ios/Runner/GeneratedPluginRegistrant.* 69 | 70 | # Exceptions to above rules. 71 | !**/ios/**/default.mode1v3 72 | !**/ios/**/default.mode2v3 73 | !**/ios/**/default.pbxuser 74 | !**/ios/**/default.perspectivev3 75 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 76 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 7736f3bc90270dcb0480db2ccffbf1d13c28db85 8 | channel: dev 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.11 2 | - Update package description 3 | 4 | ## 0.0.10 5 | - Added optional `action` parameter to save methods 6 | - Added recurring invoices 7 | - Added vendor id to invoice/quotes/... 8 | 9 | ## 0.0.9 10 | 11 | * Added support for null safety 12 | 13 | ## 0.0.8 14 | 15 | * Fixed debug bug for admin API 16 | 17 | ## 0.0.7 18 | 19 | * Fixed initialization bug for admin API 20 | 21 | ## 0.0.6 22 | 23 | * Added more models 24 | 25 | ## 0.0.5 26 | 27 | * Added company info 28 | 29 | ## 0.0.4 30 | 31 | * Added admin API 32 | 33 | ## 0.0.3 34 | 35 | * Added more API docs 36 | 37 | ## 0.0.2 38 | 39 | * Added API docs 40 | 41 | ## 0.0.1 42 | 43 | * Initial release 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Invoice Ninja 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Invoice Ninja 2 | 3 | Create PDF invoices and accept payments in a Flutter app 4 | 5 |

6 | Sample 7 |

8 | 9 | ## YouTube Video 10 | 11 | [![YouTube Video](https://img.youtube.com/vi/iefk6TOU-Ts/0.jpg)](https://www.youtube.com/watch?v=iefk6TOU-Ts) 12 | 13 | ## Features 14 | * Accept online payment in mobile, web and desktop Flutter apps 15 | * Supports many payment gateways including Stripe, PayPal and [more](https://invoiceninja.com/payments/) 16 | * Create professional [PDF invoices](https://invoiceninja.com/invoice-templates/) 17 | * Includes a self-service client portal 18 | * Many [more features](https://invoiceninja.com/features/)... 19 | 20 | ## Overview 21 | 22 | The package provides two main classes: 23 | * `InvoiceNinja`: Supports the public 'Storefront' routes which allow reading the list of products and creating/finding clients and invoices. Using this class works with restricted access to the account. 24 | * `InvoiceNinjaAdmin`: Supports the [REST Admin API](https://api-docs.invoicing.co) which uses token based security. Using this class requires an API token to access the account. 25 | 26 | ## Storefront API 27 | 28 | ### Configure 29 | 30 | ```dart 31 | InvoiceNinja.configure( 32 | 'KEY', // Set your company key or use 'KEY' to test 33 | url: 'https://demo.invoiceninja.com', // Set your selfhost app URL 34 | debugEnabled: true, 35 | ); 36 | ``` 37 | 38 | ### Load the product list 39 | 40 | ```dart 41 | final products = await InvoiceNinja.products.load(); 42 | ``` 43 | 44 | ### Find the product by key 45 | 46 | ```dart 47 | final product = await InvoiceNinja.products.findByKey('product_key'); 48 | ``` 49 | 50 | ### Create/persist the client 51 | 52 | ```dart 53 | var client = Client.forContact(email: 'test@example.com'); 54 | client = await InvoiceNinja.clients.save(client); 55 | ``` 56 | 57 | ### Create/persist the invoice 58 | 59 | ```dart 60 | var invoice = Invoice.forClient(client, products: [product]); 61 | invoice = await InvoiceNinja.invoices.save(invoice); 62 | ``` 63 | 64 | ### Display the PDF invoice 65 | ```dart 66 | launch( 67 | 'https://docs.google.com/gview?embedded=true&url=${invoice.pdfUrl}', 68 | forceWebView: true, 69 | ); 70 | ``` 71 | 72 | ### Accept the payment 73 | 74 | ```dart 75 | var invoiceKey = invoice.key; 76 | launch(invoice.url); 77 | 78 | // ... 79 | 80 | final invoice = await InvoiceNinja.invoices.findByKey(invoiceKey); 81 | if (invoice.isPaid) { 82 | // ... 83 | } 84 | ``` 85 | 86 | You can use the [WidgetsBindingObserver](https://api.flutter.dev/flutter/widgets/WidgetsBindingObserver-class.html) interface to run code when the app is resumed. 87 | 88 | Consider giving issue [#57536](https://github.com/flutter/flutter/issues/57536) a thumbs up to make this better in the future. 89 | 90 | ## Admin API 91 | 92 | ### Configure 93 | 94 | ```dart 95 | InvoiceNinjaAdmin.configure( 96 | 'TOKEN', // Set your API token or use 'TOKEN' to test 97 | url: 'https://demo.invoiceninja.com', // Set your selfhost app URL 98 | debugEnabled: true, 99 | ); 100 | ``` 101 | 102 | ### Find a client by email 103 | 104 | ```dart 105 | final client = await InvoiceNinjaAdmin.clients.findByEmail(email); 106 | ``` 107 | 108 | ### Load all invoices 109 | 110 | ```dart 111 | final payments = await InvoiceNinjaAdmin.payments.load(); 112 | ``` 113 | 114 | ### Load the payments list 115 | 116 | ```dart 117 | final payments = await InvoiceNinjaAdmin.payments.load(); 118 | ``` 119 | 120 | ### Find a payment by id 121 | 122 | ```dart 123 | final payment = await InvoiceNinjaAdmin.payments.findById(id); 124 | ``` 125 | 126 | ### Create/persist an invoice and auto-bill it 127 | 128 | ```dart 129 | var invoice = Invoice.forClient(client, products: [product]); 130 | invoice = await InvoiceNinjaAdmin.invoices.save(invoice, action: InvoiceAction.autoBill); 131 | ``` 132 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | errors: 3 | invalid_annotation_target: ignore -------------------------------------------------------------------------------- /assets/sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/assets/sample.gif -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Exceptions to above rules. 43 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 44 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 8af6b2f038c1172e61d418869363a28dffec3cb4 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # example 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.example.example" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | } 47 | 48 | buildTypes { 49 | release { 50 | // TODO: Add your own signing config for the release build. 51 | // Signing with the debug keys for now, so `flutter run --release` works. 52 | signingConfig signingConfigs.debug 53 | } 54 | } 55 | } 56 | 57 | flutter { 58 | source '../..' 59 | } 60 | 61 | dependencies { 62 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 63 | } 64 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 12 | 19 | 23 | 27 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Flutter Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | include ':app' 6 | 7 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 8 | def properties = new Properties() 9 | 10 | assert localPropertiesFile.exists() 11 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 12 | 13 | def flutterSdkPath = properties.getProperty("flutter.sdk") 14 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 15 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 16 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:invoiceninja/invoiceninja.dart'; 3 | import 'package:invoiceninja/models/client.dart'; 4 | import 'package:invoiceninja/models/invoice.dart'; 5 | import 'package:invoiceninja/models/product.dart'; 6 | import 'package:url_launcher/url_launcher.dart'; 7 | 8 | void main() { 9 | runApp(MyApp()); 10 | } 11 | 12 | class MyApp extends StatelessWidget { 13 | @override 14 | Widget build(BuildContext context) { 15 | return MaterialApp( 16 | title: 'Invoice Ninja', 17 | theme: ThemeData( 18 | brightness: Brightness.dark, 19 | ), 20 | home: MyHomePage(), 21 | ); 22 | } 23 | } 24 | 25 | class MyHomePage extends StatefulWidget { 26 | MyHomePage({Key? key}) : super(key: key); 27 | 28 | @override 29 | _MyHomePageState createState() => _MyHomePageState(); 30 | } 31 | 32 | class _MyHomePageState extends State with WidgetsBindingObserver { 33 | List _products = []; 34 | 35 | String _email = ''; 36 | Product? _product; 37 | Invoice? _invoice; 38 | 39 | @override 40 | initState() { 41 | super.initState(); 42 | WidgetsBinding.instance.addObserver(this); 43 | 44 | InvoiceNinja.configure( 45 | // Set your company key or use 'KEY' to test 46 | // The key can be generated on Settings > Client Portal 47 | 'KEY', 48 | url: 'https://demo.invoiceninja.com', // Set your selfhost app URL 49 | debugEnabled: true, 50 | ); 51 | 52 | InvoiceNinja.products.load().then((products) { 53 | setState(() { 54 | _products = products; 55 | }); 56 | }); 57 | } 58 | 59 | void _createInvoice() async { 60 | if (_product == null) { 61 | return; 62 | } 63 | 64 | var client = Client.forContact(email: _email); 65 | client = await InvoiceNinja.clients.save(client); 66 | 67 | var invoice = Invoice.forClient(client, products: [_product!]); 68 | invoice = await InvoiceNinja.invoices.save(invoice); 69 | 70 | setState(() { 71 | _invoice = invoice; 72 | }); 73 | } 74 | 75 | void _viewPdf() { 76 | if (_invoice == null) { 77 | return; 78 | } 79 | 80 | launch( 81 | 'https://docs.google.com/gview?embedded=true&url=${_invoice!.pdfUrl}', 82 | forceWebView: true, 83 | ); 84 | } 85 | 86 | void _viewPortal() { 87 | if (_invoice == null) { 88 | return; 89 | } 90 | 91 | final invitation = _invoice!.invitations.first; 92 | launch(invitation.url); 93 | } 94 | 95 | @override 96 | void didChangeAppLifecycleState(AppLifecycleState state) async { 97 | if (_invoice == null || state != AppLifecycleState.resumed) { 98 | return; 99 | } 100 | 101 | final invoice = await InvoiceNinja.invoices.findByKey(_invoice!.key); 102 | 103 | if (invoice.isPaid) { 104 | // ... 105 | } 106 | } 107 | 108 | @override 109 | void dispose() { 110 | WidgetsBinding.instance.removeObserver(this); 111 | super.dispose(); 112 | } 113 | 114 | @override 115 | Widget build(BuildContext context) { 116 | return Scaffold( 117 | appBar: AppBar( 118 | title: Text('Invoice Ninja Example'), 119 | ), 120 | body: SingleChildScrollView( 121 | child: Padding( 122 | padding: const EdgeInsets.all(16), 123 | child: Card( 124 | child: Padding( 125 | padding: const EdgeInsets.all(16), 126 | child: Column( 127 | mainAxisSize: MainAxisSize.min, 128 | crossAxisAlignment: CrossAxisAlignment.stretch, 129 | children: [ 130 | TextFormField( 131 | decoration: InputDecoration( 132 | labelText: 'Email', 133 | suffixIcon: Icon(Icons.email), 134 | ), 135 | onChanged: (value) => setState(() => _email = value), 136 | keyboardType: TextInputType.emailAddress, 137 | ), 138 | DropdownButtonFormField( 139 | decoration: InputDecoration( 140 | labelText: 'Product', 141 | ), 142 | onChanged: (value) => setState(() => _product = value), 143 | items: _products 144 | .map((product) => DropdownMenuItem( 145 | child: Text(product.productKey), 146 | value: product, 147 | )) 148 | .toList(), 149 | ), 150 | SizedBox(height: 16), 151 | OutlinedButton( 152 | child: Text('Create Invoice'), 153 | onPressed: (_email.isNotEmpty && _product != null) 154 | ? () => _createInvoice() 155 | : null, 156 | ), 157 | OutlinedButton( 158 | child: Text('View PDF'), 159 | onPressed: (_invoice != null) ? () => _viewPdf() : null, 160 | ), 161 | OutlinedButton( 162 | child: Text('View Portal'), 163 | onPressed: (_invoice != null) ? () => _viewPortal() : null, 164 | ), 165 | ], 166 | ), 167 | ), 168 | ), 169 | ), 170 | ), 171 | ); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /example/linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /example/linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(runner LANGUAGES CXX) 3 | 4 | set(BINARY_NAME "example") 5 | set(APPLICATION_ID "com.example.example") 6 | 7 | cmake_policy(SET CMP0063 NEW) 8 | 9 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 10 | 11 | # Configure build options. 12 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 13 | set(CMAKE_BUILD_TYPE "Debug" CACHE 14 | STRING "Flutter build mode" FORCE) 15 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 16 | "Debug" "Profile" "Release") 17 | endif() 18 | 19 | # Compilation settings that should be applied to most targets. 20 | function(APPLY_STANDARD_SETTINGS TARGET) 21 | target_compile_features(${TARGET} PUBLIC cxx_std_14) 22 | target_compile_options(${TARGET} PRIVATE -Wall -Werror) 23 | target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") 24 | target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") 25 | endfunction() 26 | 27 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 28 | 29 | # Flutter library and tool build rules. 30 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 31 | 32 | # System-level dependencies. 33 | find_package(PkgConfig REQUIRED) 34 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 35 | 36 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") 37 | 38 | # Application build 39 | add_executable(${BINARY_NAME} 40 | "main.cc" 41 | "my_application.cc" 42 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 43 | ) 44 | apply_standard_settings(${BINARY_NAME}) 45 | target_link_libraries(${BINARY_NAME} PRIVATE flutter) 46 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) 47 | add_dependencies(${BINARY_NAME} flutter_assemble) 48 | # Only the install-generated bundle's copy of the executable will launch 49 | # correctly, since the resources must in the right relative locations. To avoid 50 | # people trying to run the unbundled copy, put it in a subdirectory instead of 51 | # the default top-level location. 52 | set_target_properties(${BINARY_NAME} 53 | PROPERTIES 54 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" 55 | ) 56 | 57 | # Generated plugin build rules, which manage building the plugins and adding 58 | # them to the application. 59 | include(flutter/generated_plugins.cmake) 60 | 61 | 62 | # === Installation === 63 | # By default, "installing" just makes a relocatable bundle in the build 64 | # directory. 65 | set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") 66 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 67 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 68 | endif() 69 | 70 | # Start with a clean build bundle directory every time. 71 | install(CODE " 72 | file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") 73 | " COMPONENT Runtime) 74 | 75 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 76 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") 77 | 78 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 79 | COMPONENT Runtime) 80 | 81 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 82 | COMPONENT Runtime) 83 | 84 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 85 | COMPONENT Runtime) 86 | 87 | if(PLUGIN_BUNDLED_LIBRARIES) 88 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 89 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 90 | COMPONENT Runtime) 91 | endif() 92 | 93 | # Fully re-copy the assets directory on each build to avoid having stale files 94 | # from a previous install. 95 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 96 | install(CODE " 97 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 98 | " COMPONENT Runtime) 99 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 100 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 101 | 102 | # Install the AOT library on non-Debug builds only. 103 | if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") 104 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 105 | COMPONENT Runtime) 106 | endif() 107 | -------------------------------------------------------------------------------- /example/linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 4 | 5 | # Configuration provided via flutter tool. 6 | include(${EPHEMERAL_DIR}/generated_config.cmake) 7 | 8 | # TODO: Move the rest of this into files in ephemeral. See 9 | # https://github.com/flutter/flutter/issues/57146. 10 | 11 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 12 | # which isn't available in 3.10. 13 | function(list_prepend LIST_NAME PREFIX) 14 | set(NEW_LIST "") 15 | foreach(element ${${LIST_NAME}}) 16 | list(APPEND NEW_LIST "${PREFIX}${element}") 17 | endforeach(element) 18 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 19 | endfunction() 20 | 21 | # === Flutter Library === 22 | # System-level dependencies. 23 | find_package(PkgConfig REQUIRED) 24 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 25 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 26 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 27 | pkg_check_modules(BLKID REQUIRED IMPORTED_TARGET blkid) 28 | pkg_check_modules(LZMA REQUIRED IMPORTED_TARGET liblzma) 29 | 30 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 31 | 32 | # Published to parent scope for install step. 33 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 34 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 35 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 36 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 37 | 38 | list(APPEND FLUTTER_LIBRARY_HEADERS 39 | "fl_basic_message_channel.h" 40 | "fl_binary_codec.h" 41 | "fl_binary_messenger.h" 42 | "fl_dart_project.h" 43 | "fl_engine.h" 44 | "fl_json_message_codec.h" 45 | "fl_json_method_codec.h" 46 | "fl_message_codec.h" 47 | "fl_method_call.h" 48 | "fl_method_channel.h" 49 | "fl_method_codec.h" 50 | "fl_method_response.h" 51 | "fl_plugin_registrar.h" 52 | "fl_plugin_registry.h" 53 | "fl_standard_message_codec.h" 54 | "fl_standard_method_codec.h" 55 | "fl_string_codec.h" 56 | "fl_value.h" 57 | "fl_view.h" 58 | "flutter_linux.h" 59 | ) 60 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 61 | add_library(flutter INTERFACE) 62 | target_include_directories(flutter INTERFACE 63 | "${EPHEMERAL_DIR}" 64 | ) 65 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 66 | target_link_libraries(flutter INTERFACE 67 | PkgConfig::GTK 68 | PkgConfig::GLIB 69 | PkgConfig::GIO 70 | PkgConfig::BLKID 71 | PkgConfig::LZMA 72 | ) 73 | add_dependencies(flutter flutter_assemble) 74 | 75 | # === Flutter tool backend === 76 | # _phony_ is a non-existent file to force this command to run every time, 77 | # since currently there's no way to get a full input/output list from the 78 | # flutter tool. 79 | add_custom_command( 80 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 81 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 82 | COMMAND ${CMAKE_COMMAND} -E env 83 | ${FLUTTER_TOOL_ENVIRONMENT} 84 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 85 | linux-x64 ${CMAKE_BUILD_TYPE} 86 | VERBATIM 87 | ) 88 | add_custom_target(flutter_assemble DEPENDS 89 | "${FLUTTER_LIBRARY}" 90 | ${FLUTTER_LIBRARY_HEADERS} 91 | ) 92 | -------------------------------------------------------------------------------- /example/linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | 11 | void fl_register_plugins(FlPluginRegistry* registry) { 12 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 13 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 14 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 15 | } 16 | -------------------------------------------------------------------------------- /example/linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /example/linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | url_launcher_linux 7 | ) 8 | 9 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 10 | ) 11 | 12 | set(PLUGIN_BUNDLED_LIBRARIES) 13 | 14 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 15 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 16 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 19 | endforeach(plugin) 20 | 21 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 22 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 23 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 24 | endforeach(ffi_plugin) 25 | -------------------------------------------------------------------------------- /example/linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /example/linux/my_application.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | #include 4 | #ifdef GDK_WINDOWING_X11 5 | #include 6 | #endif 7 | 8 | #include "flutter/generated_plugin_registrant.h" 9 | 10 | struct _MyApplication { 11 | GtkApplication parent_instance; 12 | char** dart_entrypoint_arguments; 13 | }; 14 | 15 | G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) 16 | 17 | // Implements GApplication::activate. 18 | static void my_application_activate(GApplication* application) { 19 | MyApplication* self = MY_APPLICATION(application); 20 | GtkWindow* window = 21 | GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); 22 | 23 | // Use a header bar when running in GNOME as this is the common style used 24 | // by applications and is the setup most users will be using (e.g. Ubuntu 25 | // desktop). 26 | // If running on X and not using GNOME then just use a traditional title bar 27 | // in case the window manager does more exotic layout, e.g. tiling. 28 | // If running on Wayland assume the header bar will work (may need changing 29 | // if future cases occur). 30 | gboolean use_header_bar = TRUE; 31 | #ifdef GDK_WINDOWING_X11 32 | GdkScreen *screen = gtk_window_get_screen(window); 33 | if (GDK_IS_X11_SCREEN(screen)) { 34 | const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); 35 | if (g_strcmp0(wm_name, "GNOME Shell") != 0) { 36 | use_header_bar = FALSE; 37 | } 38 | } 39 | #endif 40 | if (use_header_bar) { 41 | GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); 42 | gtk_widget_show(GTK_WIDGET(header_bar)); 43 | gtk_header_bar_set_title(header_bar, "example"); 44 | gtk_header_bar_set_show_close_button(header_bar, TRUE); 45 | gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); 46 | } 47 | else { 48 | gtk_window_set_title(window, "example"); 49 | } 50 | 51 | gtk_window_set_default_size(window, 1280, 720); 52 | gtk_widget_show(GTK_WIDGET(window)); 53 | 54 | g_autoptr(FlDartProject) project = fl_dart_project_new(); 55 | fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); 56 | 57 | FlView* view = fl_view_new(project); 58 | gtk_widget_show(GTK_WIDGET(view)); 59 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); 60 | 61 | fl_register_plugins(FL_PLUGIN_REGISTRY(view)); 62 | 63 | gtk_widget_grab_focus(GTK_WIDGET(view)); 64 | } 65 | 66 | // Implements GApplication::local_command_line. 67 | static gboolean my_application_local_command_line(GApplication* application, gchar ***arguments, int *exit_status) { 68 | MyApplication* self = MY_APPLICATION(application); 69 | // Strip out the first argument as it is the binary name. 70 | self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); 71 | 72 | g_autoptr(GError) error = nullptr; 73 | if (!g_application_register(application, nullptr, &error)) { 74 | g_warning("Failed to register: %s", error->message); 75 | *exit_status = 1; 76 | return TRUE; 77 | } 78 | 79 | g_application_activate(application); 80 | *exit_status = 0; 81 | 82 | return TRUE; 83 | } 84 | 85 | // Implements GObject::dispose. 86 | static void my_application_dispose(GObject *object) { 87 | MyApplication* self = MY_APPLICATION(object); 88 | g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); 89 | G_OBJECT_CLASS(my_application_parent_class)->dispose(object); 90 | } 91 | 92 | static void my_application_class_init(MyApplicationClass* klass) { 93 | G_APPLICATION_CLASS(klass)->activate = my_application_activate; 94 | G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; 95 | G_OBJECT_CLASS(klass)->dispose = my_application_dispose; 96 | } 97 | 98 | static void my_application_init(MyApplication* self) {} 99 | 100 | MyApplication* my_application_new() { 101 | return MY_APPLICATION(g_object_new(my_application_get_type(), 102 | "application-id", APPLICATION_ID, 103 | nullptr)); 104 | } 105 | -------------------------------------------------------------------------------- /example/linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 0.0.1+1 19 | 20 | environment: 21 | sdk: ">=2.12.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | url_launcher: ^6.0.3 27 | invoiceninja: ^0.0.11 28 | #invoiceninja: 29 | # git: 30 | # url: git://github.com/invoiceninja/flutter-package.git 31 | 32 | 33 | # The following adds the Cupertino Icons font to your application. 34 | # Use with the CupertinoIcons class for iOS style icons. 35 | cupertino_icons: ^1.0.2 36 | 37 | dev_dependencies: 38 | flutter_test: 39 | sdk: flutter 40 | analyzer: any 41 | 42 | # For information on the generic Dart part of this file, see the 43 | # following page: https://dart.dev/tools/pub/pubspec 44 | 45 | # The following section is specific to Flutter. 46 | flutter: 47 | 48 | # The following line ensures that the Material Icons font is 49 | # included with your application, so that you can use the icons in 50 | # the material Icons class. 51 | uses-material-design: true 52 | 53 | # To add assets to your application, add an assets section, like this: 54 | # assets: 55 | # - images/a_dot_burr.jpeg 56 | # - images/a_dot_ham.jpeg 57 | 58 | # An image asset can refer to one or more resolution-specific "variants", see 59 | # https://flutter.dev/assets-and-images/#resolution-aware. 60 | 61 | # For details regarding adding assets from package dependencies, see 62 | # https://flutter.dev/assets-and-images/#from-packages 63 | 64 | # To add custom fonts to your application, add a fonts section here, 65 | # in this "flutter" section. Each entry in this list should have a 66 | # "family" key with the font family name, and a "fonts" key with a 67 | # list giving the asset and other descriptors for the font. For 68 | # example: 69 | # fonts: 70 | # - family: Schyler 71 | # fonts: 72 | # - asset: fonts/Schyler-Regular.ttf 73 | # - asset: fonts/Schyler-Italic.ttf 74 | # style: italic 75 | # - family: Trajan Pro 76 | # fonts: 77 | # - asset: fonts/TrajanPro.ttf 78 | # - asset: fonts/TrajanPro_Bold.ttf 79 | # weight: 700 80 | # 81 | # For details regarding fonts from package dependencies, 82 | # see https://flutter.dev/custom-fonts/#from-packages 83 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/web/favicon.png -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | example 30 | 31 | 32 | 33 | 36 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "short_name": "example", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /example/windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /example/windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(example LANGUAGES CXX) 3 | 4 | set(BINARY_NAME "example") 5 | 6 | cmake_policy(SET CMP0063 NEW) 7 | 8 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 9 | 10 | # Configure build options. 11 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 12 | if(IS_MULTICONFIG) 13 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 14 | CACHE STRING "" FORCE) 15 | else() 16 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 17 | set(CMAKE_BUILD_TYPE "Debug" CACHE 18 | STRING "Flutter build mode" FORCE) 19 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 20 | "Debug" "Profile" "Release") 21 | endif() 22 | endif() 23 | 24 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 25 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 26 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 27 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 28 | 29 | # Use Unicode for all projects. 30 | add_definitions(-DUNICODE -D_UNICODE) 31 | 32 | # Compilation settings that should be applied to most targets. 33 | function(APPLY_STANDARD_SETTINGS TARGET) 34 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 35 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 36 | target_compile_options(${TARGET} PRIVATE /EHsc) 37 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 38 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 39 | endfunction() 40 | 41 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 42 | 43 | # Flutter library and tool build rules. 44 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 45 | 46 | # Application build 47 | add_subdirectory("runner") 48 | 49 | # Generated plugin build rules, which manage building the plugins and adding 50 | # them to the application. 51 | include(flutter/generated_plugins.cmake) 52 | 53 | 54 | # === Installation === 55 | # Support files are copied into place next to the executable, so that it can 56 | # run in place. This is done instead of making a separate bundle (as on Linux) 57 | # so that building and running from within Visual Studio will work. 58 | set(BUILD_BUNDLE_DIR "$") 59 | # Make the "install" step default, as it's required to run. 60 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 61 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 62 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 63 | endif() 64 | 65 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 66 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 67 | 68 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 69 | COMPONENT Runtime) 70 | 71 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 72 | COMPONENT Runtime) 73 | 74 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 75 | COMPONENT Runtime) 76 | 77 | if(PLUGIN_BUNDLED_LIBRARIES) 78 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 79 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 80 | COMPONENT Runtime) 81 | endif() 82 | 83 | # Fully re-copy the assets directory on each build to avoid having stale files 84 | # from a previous install. 85 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 86 | install(CODE " 87 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 88 | " COMPONENT Runtime) 89 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 90 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 91 | 92 | # Install the AOT library on non-Debug builds only. 93 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 94 | CONFIGURATIONS Profile;Release 95 | COMPONENT Runtime) 96 | -------------------------------------------------------------------------------- /example/windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 4 | 5 | # Configuration provided via flutter tool. 6 | include(${EPHEMERAL_DIR}/generated_config.cmake) 7 | 8 | # TODO: Move the rest of this into files in ephemeral. See 9 | # https://github.com/flutter/flutter/issues/57146. 10 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 11 | 12 | # === Flutter Library === 13 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 14 | 15 | # Published to parent scope for install step. 16 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 17 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 18 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 19 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 20 | 21 | list(APPEND FLUTTER_LIBRARY_HEADERS 22 | "flutter_export.h" 23 | "flutter_windows.h" 24 | "flutter_messenger.h" 25 | "flutter_plugin_registrar.h" 26 | "flutter_texture_registrar.h" 27 | ) 28 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 29 | add_library(flutter INTERFACE) 30 | target_include_directories(flutter INTERFACE 31 | "${EPHEMERAL_DIR}" 32 | ) 33 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 34 | add_dependencies(flutter flutter_assemble) 35 | 36 | # === Wrapper === 37 | list(APPEND CPP_WRAPPER_SOURCES_CORE 38 | "core_implementations.cc" 39 | "standard_codec.cc" 40 | ) 41 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 42 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 43 | "plugin_registrar.cc" 44 | ) 45 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 46 | list(APPEND CPP_WRAPPER_SOURCES_APP 47 | "flutter_engine.cc" 48 | "flutter_view_controller.cc" 49 | ) 50 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 51 | 52 | # Wrapper sources needed for a plugin. 53 | add_library(flutter_wrapper_plugin STATIC 54 | ${CPP_WRAPPER_SOURCES_CORE} 55 | ${CPP_WRAPPER_SOURCES_PLUGIN} 56 | ) 57 | apply_standard_settings(flutter_wrapper_plugin) 58 | set_target_properties(flutter_wrapper_plugin PROPERTIES 59 | POSITION_INDEPENDENT_CODE ON) 60 | set_target_properties(flutter_wrapper_plugin PROPERTIES 61 | CXX_VISIBILITY_PRESET hidden) 62 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 63 | target_include_directories(flutter_wrapper_plugin PUBLIC 64 | "${WRAPPER_ROOT}/include" 65 | ) 66 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 67 | 68 | # Wrapper sources needed for the runner. 69 | add_library(flutter_wrapper_app STATIC 70 | ${CPP_WRAPPER_SOURCES_CORE} 71 | ${CPP_WRAPPER_SOURCES_APP} 72 | ) 73 | apply_standard_settings(flutter_wrapper_app) 74 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 75 | target_include_directories(flutter_wrapper_app PUBLIC 76 | "${WRAPPER_ROOT}/include" 77 | ) 78 | add_dependencies(flutter_wrapper_app flutter_assemble) 79 | 80 | # === Flutter tool backend === 81 | # _phony_ is a non-existent file to force this command to run every time, 82 | # since currently there's no way to get a full input/output list from the 83 | # flutter tool. 84 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 85 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 86 | add_custom_command( 87 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 88 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 89 | ${CPP_WRAPPER_SOURCES_APP} 90 | ${PHONY_OUTPUT} 91 | COMMAND ${CMAKE_COMMAND} -E env 92 | ${FLUTTER_TOOL_ENVIRONMENT} 93 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 94 | windows-x64 $ 95 | VERBATIM 96 | ) 97 | add_custom_target(flutter_assemble DEPENDS 98 | "${FLUTTER_LIBRARY}" 99 | ${FLUTTER_LIBRARY_HEADERS} 100 | ${CPP_WRAPPER_SOURCES_CORE} 101 | ${CPP_WRAPPER_SOURCES_PLUGIN} 102 | ${CPP_WRAPPER_SOURCES_APP} 103 | ) 104 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | 11 | void RegisterPlugins(flutter::PluginRegistry* registry) { 12 | UrlLauncherPluginRegisterWithRegistrar( 13 | registry->GetRegistrarForPlugin("UrlLauncherPlugin")); 14 | } 15 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | url_launcher_windows 7 | ) 8 | 9 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 10 | ) 11 | 12 | set(PLUGIN_BUNDLED_LIBRARIES) 13 | 14 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 15 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 16 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 19 | endforeach(plugin) 20 | 21 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 22 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 23 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 24 | endforeach(ffi_plugin) 25 | -------------------------------------------------------------------------------- /example/windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(runner LANGUAGES CXX) 3 | 4 | add_executable(${BINARY_NAME} WIN32 5 | "flutter_window.cpp" 6 | "main.cpp" 7 | "run_loop.cpp" 8 | "utils.cpp" 9 | "win32_window.cpp" 10 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 11 | "Runner.rc" 12 | "runner.exe.manifest" 13 | ) 14 | apply_standard_settings(${BINARY_NAME}) 15 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 16 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 17 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 18 | add_dependencies(${BINARY_NAME} flutter_assemble) 19 | -------------------------------------------------------------------------------- /example/windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #ifdef FLUTTER_BUILD_NUMBER 64 | #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0 67 | #endif 68 | 69 | #ifdef FLUTTER_BUILD_NAME 70 | #define VERSION_AS_STRING #FLUTTER_BUILD_NAME 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "com.example" "\0" 93 | VALUE "FileDescription", "A new Flutter project." "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "example" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2021 com.example. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "example.exe" "\0" 98 | VALUE "ProductName", "example" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(RunLoop* run_loop, 8 | const flutter::DartProject& project) 9 | : run_loop_(run_loop), project_(project) {} 10 | 11 | FlutterWindow::~FlutterWindow() {} 12 | 13 | bool FlutterWindow::OnCreate() { 14 | if (!Win32Window::OnCreate()) { 15 | return false; 16 | } 17 | 18 | RECT frame = GetClientArea(); 19 | 20 | // The size here must match the window dimensions to avoid unnecessary surface 21 | // creation / destruction in the startup path. 22 | flutter_controller_ = std::make_unique( 23 | frame.right - frame.left, frame.bottom - frame.top, project_); 24 | // Ensure that basic setup of the controller was successful. 25 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 26 | return false; 27 | } 28 | RegisterPlugins(flutter_controller_->engine()); 29 | run_loop_->RegisterFlutterInstance(flutter_controller_->engine()); 30 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 31 | return true; 32 | } 33 | 34 | void FlutterWindow::OnDestroy() { 35 | if (flutter_controller_) { 36 | run_loop_->UnregisterFlutterInstance(flutter_controller_->engine()); 37 | flutter_controller_ = nullptr; 38 | } 39 | 40 | Win32Window::OnDestroy(); 41 | } 42 | 43 | LRESULT 44 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 45 | WPARAM const wparam, 46 | LPARAM const lparam) noexcept { 47 | // Give Flutter, including plugins, an opporutunity to handle window messages. 48 | if (flutter_controller_) { 49 | std::optional result = 50 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 51 | lparam); 52 | if (result) { 53 | return *result; 54 | } 55 | } 56 | 57 | switch (message) { 58 | case WM_FONTCHANGE: 59 | flutter_controller_->engine()->ReloadSystemFonts(); 60 | break; 61 | } 62 | 63 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 64 | } 65 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "run_loop.h" 10 | #include "win32_window.h" 11 | 12 | // A window that does nothing but host a Flutter view. 13 | class FlutterWindow : public Win32Window { 14 | public: 15 | // Creates a new FlutterWindow driven by the |run_loop|, hosting a 16 | // Flutter view running |project|. 17 | explicit FlutterWindow(RunLoop* run_loop, 18 | const flutter::DartProject& project); 19 | virtual ~FlutterWindow(); 20 | 21 | protected: 22 | // Win32Window: 23 | bool OnCreate() override; 24 | void OnDestroy() override; 25 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 26 | LPARAM const lparam) noexcept override; 27 | 28 | private: 29 | // The run loop driving events for this window. 30 | RunLoop* run_loop_; 31 | 32 | // The project to run. 33 | flutter::DartProject project_; 34 | 35 | // The Flutter instance hosted by this window. 36 | std::unique_ptr flutter_controller_; 37 | }; 38 | 39 | #endif // RUNNER_FLUTTER_WINDOW_H_ 40 | -------------------------------------------------------------------------------- /example/windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "run_loop.h" 7 | #include "utils.h" 8 | 9 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 10 | _In_ wchar_t *command_line, _In_ int show_command) { 11 | // Attach to console when present (e.g., 'flutter run') or create a 12 | // new console when running with a debugger. 13 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 14 | CreateAndAttachConsole(); 15 | } 16 | 17 | // Initialize COM, so that it is available for use in the library and/or 18 | // plugins. 19 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 20 | 21 | RunLoop run_loop; 22 | 23 | flutter::DartProject project(L"data"); 24 | 25 | std::vector command_line_arguments = 26 | GetCommandLineArguments(); 27 | 28 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 29 | 30 | FlutterWindow window(&run_loop, project); 31 | Win32Window::Point origin(10, 10); 32 | Win32Window::Size size(1280, 720); 33 | if (!window.CreateAndShow(L"example", origin, size)) { 34 | return EXIT_FAILURE; 35 | } 36 | window.SetQuitOnClose(true); 37 | 38 | run_loop.Run(); 39 | 40 | ::CoUninitialize(); 41 | return EXIT_SUCCESS; 42 | } 43 | -------------------------------------------------------------------------------- /example/windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /example/windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/invoiceninja/flutter-package/a7790ef8c1132470da61244b2a975b91135b6f7f/example/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /example/windows/runner/run_loop.cpp: -------------------------------------------------------------------------------- 1 | #include "run_loop.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | RunLoop::RunLoop() {} 8 | 9 | RunLoop::~RunLoop() {} 10 | 11 | void RunLoop::Run() { 12 | bool keep_running = true; 13 | TimePoint next_flutter_event_time = TimePoint::clock::now(); 14 | while (keep_running) { 15 | std::chrono::nanoseconds wait_duration = 16 | std::max(std::chrono::nanoseconds(0), 17 | next_flutter_event_time - TimePoint::clock::now()); 18 | ::MsgWaitForMultipleObjects( 19 | 0, nullptr, FALSE, static_cast(wait_duration.count() / 1000), 20 | QS_ALLINPUT); 21 | bool processed_events = false; 22 | MSG message; 23 | // All pending Windows messages must be processed; MsgWaitForMultipleObjects 24 | // won't return again for items left in the queue after PeekMessage. 25 | while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) { 26 | processed_events = true; 27 | if (message.message == WM_QUIT) { 28 | keep_running = false; 29 | break; 30 | } 31 | ::TranslateMessage(&message); 32 | ::DispatchMessage(&message); 33 | // Allow Flutter to process messages each time a Windows message is 34 | // processed, to prevent starvation. 35 | next_flutter_event_time = 36 | std::min(next_flutter_event_time, ProcessFlutterMessages()); 37 | } 38 | // If the PeekMessage loop didn't run, process Flutter messages. 39 | if (!processed_events) { 40 | next_flutter_event_time = 41 | std::min(next_flutter_event_time, ProcessFlutterMessages()); 42 | } 43 | } 44 | } 45 | 46 | void RunLoop::RegisterFlutterInstance( 47 | flutter::FlutterEngine* flutter_instance) { 48 | flutter_instances_.insert(flutter_instance); 49 | } 50 | 51 | void RunLoop::UnregisterFlutterInstance( 52 | flutter::FlutterEngine* flutter_instance) { 53 | flutter_instances_.erase(flutter_instance); 54 | } 55 | 56 | RunLoop::TimePoint RunLoop::ProcessFlutterMessages() { 57 | TimePoint next_event_time = TimePoint::max(); 58 | for (auto instance : flutter_instances_) { 59 | std::chrono::nanoseconds wait_duration = instance->ProcessMessages(); 60 | if (wait_duration != std::chrono::nanoseconds::max()) { 61 | next_event_time = 62 | std::min(next_event_time, TimePoint::clock::now() + wait_duration); 63 | } 64 | } 65 | return next_event_time; 66 | } 67 | -------------------------------------------------------------------------------- /example/windows/runner/run_loop.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_RUN_LOOP_H_ 2 | #define RUNNER_RUN_LOOP_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | // A runloop that will service events for Flutter instances as well 10 | // as native messages. 11 | class RunLoop { 12 | public: 13 | RunLoop(); 14 | ~RunLoop(); 15 | 16 | // Prevent copying 17 | RunLoop(RunLoop const&) = delete; 18 | RunLoop& operator=(RunLoop const&) = delete; 19 | 20 | // Runs the run loop until the application quits. 21 | void Run(); 22 | 23 | // Registers the given Flutter instance for event servicing. 24 | void RegisterFlutterInstance( 25 | flutter::FlutterEngine* flutter_instance); 26 | 27 | // Unregisters the given Flutter instance from event servicing. 28 | void UnregisterFlutterInstance( 29 | flutter::FlutterEngine* flutter_instance); 30 | 31 | private: 32 | using TimePoint = std::chrono::steady_clock::time_point; 33 | 34 | // Processes all currently pending messages for registered Flutter instances. 35 | TimePoint ProcessFlutterMessages(); 36 | 37 | std::set flutter_instances_; 38 | }; 39 | 40 | #endif // RUNNER_RUN_LOOP_H_ 41 | -------------------------------------------------------------------------------- /example/windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /example/windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr); 51 | if (target_length == 0) { 52 | return std::string(); 53 | } 54 | std::string utf8_string; 55 | utf8_string.resize(target_length); 56 | int converted_length = ::WideCharToMultiByte( 57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 58 | -1, utf8_string.data(), 59 | target_length, nullptr, nullptr); 60 | if (converted_length == 0) { 61 | return std::string(); 62 | } 63 | return utf8_string; 64 | } 65 | -------------------------------------------------------------------------------- /example/windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /example/windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates and shows a win32 window with |title| and position and size using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size to will treat the width height passed in to this function 35 | // as logical pixels and scale to appropriate for the default monitor. Returns 36 | // true if the window was created successfully. 37 | bool CreateAndShow(const std::wstring& title, 38 | const Point& origin, 39 | const Size& size); 40 | 41 | // Release OS resources associated with window. 42 | void Destroy(); 43 | 44 | // Inserts |content| into the window tree. 45 | void SetChildContent(HWND content); 46 | 47 | // Returns the backing Window handle to enable clients to set icon and other 48 | // window properties. Returns nullptr if the window has been destroyed. 49 | HWND GetHandle(); 50 | 51 | // If true, closing this window will quit the application. 52 | void SetQuitOnClose(bool quit_on_close); 53 | 54 | // Return a RECT representing the bounds of the current client area. 55 | RECT GetClientArea(); 56 | 57 | protected: 58 | // Processes and route salient window messages for mouse handling, 59 | // size change and DPI. Delegates handling of these to member overloads that 60 | // inheriting classes can handle. 61 | virtual LRESULT MessageHandler(HWND window, 62 | UINT const message, 63 | WPARAM const wparam, 64 | LPARAM const lparam) noexcept; 65 | 66 | // Called when CreateAndShow is called, allowing subclass window-related 67 | // setup. Subclasses should return false if setup fails. 68 | virtual bool OnCreate(); 69 | 70 | // Called when Destroy is called. 71 | virtual void OnDestroy(); 72 | 73 | private: 74 | friend class WindowClassRegistrar; 75 | 76 | // OS callback called by message pump. Handles the WM_NCCREATE message which 77 | // is passed when the non-client area is being created and enables automatic 78 | // non-client DPI scaling so that the non-client area automatically 79 | // responsponds to changes in DPI. All other messages are handled by 80 | // MessageHandler. 81 | static LRESULT CALLBACK WndProc(HWND const window, 82 | UINT const message, 83 | WPARAM const wparam, 84 | LPARAM const lparam) noexcept; 85 | 86 | // Retrieves a class instance pointer for |window| 87 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 88 | 89 | bool quit_on_close_ = false; 90 | 91 | // window handle for top level window. 92 | HWND window_handle_ = nullptr; 93 | 94 | // window handle for hosted content. 95 | HWND child_content_ = nullptr; 96 | }; 97 | 98 | #endif // RUNNER_WIN32_WINDOW_H_ 99 | -------------------------------------------------------------------------------- /lib/invoiceninja.dart: -------------------------------------------------------------------------------- 1 | library invoiceninja; 2 | 3 | import 'package:invoiceninja/repositories/recurring_invoice_repository.dart'; 4 | 5 | import 'repositories/client_repository.dart'; 6 | import 'repositories/company_repository.dart'; 7 | import 'repositories/credit_repository.dart'; 8 | import 'repositories/invoice_repository.dart'; 9 | import 'repositories/payment_repository.dart'; 10 | import 'repositories/product_repository.dart'; 11 | import 'repositories/quote_repository.dart'; 12 | 13 | /// Storefront SDK for Invoice Ninja 14 | class InvoiceNinja { 15 | static String url = 'https://app.invoicing.co'; 16 | static late String companyKey; 17 | static late bool? debugEnabled; 18 | 19 | static CompanyRepository company = CompanyRepository(); 20 | static ProductRepository products = ProductRepository(); 21 | static ClientRepository clients = ClientRepository(); 22 | static InvoiceRepository invoices = InvoiceRepository(); 23 | 24 | /// Configure the Invoice Ninja package 25 | /// The key can be generated on Settings > Client Portal 26 | static void configure( 27 | String companyKey, { 28 | bool debugEnabled = false, 29 | String? url, 30 | }) { 31 | InvoiceNinja.companyKey = companyKey; 32 | InvoiceNinja.debugEnabled = debugEnabled; 33 | 34 | if (url != null) { 35 | if (!url.startsWith('http')) { 36 | url = 'https://$url'; 37 | } 38 | 39 | InvoiceNinja.url = url; 40 | } 41 | } 42 | 43 | /// Check if the package has been initialized 44 | static bool get isInitialized => debugEnabled != null; 45 | } 46 | 47 | /// Admin SDK for Invoice Ninja 48 | class InvoiceNinjaAdmin { 49 | static String url = 'https://app.invoicing.co'; 50 | static late String token; 51 | static late bool? debugEnabled; 52 | 53 | static ProductAdminRepository products = ProductAdminRepository(); 54 | static ClientAdminRepository clients = ClientAdminRepository(); 55 | static InvoiceAdminRepository invoices = InvoiceAdminRepository(); 56 | static PaymentAdminRepository payments = PaymentAdminRepository(); 57 | static QuoteAdminRepository quotes = QuoteAdminRepository(); 58 | static CreditAdminRepository credits = CreditAdminRepository(); 59 | static RecurringInvoiceAdminRepository recurringInvoices = 60 | RecurringInvoiceAdminRepository(); 61 | 62 | /// Configure the Invoice Ninja package 63 | /// Tokens can be managed on Settings > Account Management 64 | static void configure( 65 | String token, { 66 | bool debugEnabled = false, 67 | String? url, 68 | }) { 69 | InvoiceNinjaAdmin.token = token; 70 | InvoiceNinjaAdmin.debugEnabled = debugEnabled; 71 | 72 | if (url != null) { 73 | if (!url.startsWith('http')) { 74 | url = 'https://$url'; 75 | } 76 | 77 | InvoiceNinjaAdmin.url = url; 78 | } 79 | } 80 | 81 | /// Check if the package has been initialized 82 | static bool get isInitialized => debugEnabled != null; 83 | } 84 | -------------------------------------------------------------------------------- /lib/models/client.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:invoiceninja/models/document.dart'; 4 | 5 | part 'client.freezed.dart'; 6 | 7 | part 'client.g.dart'; 8 | 9 | /// Client class 10 | @freezed 11 | class Client with _$Client { 12 | /// Default constructor 13 | const Client._(); 14 | 15 | /// Client factory constructor 16 | @JsonSerializable(explicitToJson: true) 17 | const factory Client({ 18 | @Default('') String id, 19 | @Default('') @JsonKey(name: 'user_id') String createdById, 20 | @Default('') @JsonKey(name: 'assigned_user_id') String assignedToId, 21 | @Default(0) @JsonKey(name: 'created_at') int createdAt, 22 | @Default(0) @JsonKey(name: 'updated_at') int updatedAt, 23 | @Default(0) @JsonKey(name: 'archived_at') int archivedAt, 24 | @Default(false) @JsonKey(name: 'is_deleted') bool isDeleted, 25 | @Default('') @JsonKey(name: 'custom_value1') String customValue1, 26 | @Default('') @JsonKey(name: 'custom_value2') String customValue2, 27 | @Default('') @JsonKey(name: 'custom_value3') String customValue3, 28 | @Default('') @JsonKey(name: 'custom_value4') String customValue4, 29 | @Default('') String name, 30 | @Default('') String website, 31 | @Default('') @JsonKey(name: 'private_notes') String privateNotes, 32 | @Default(0) double balance, 33 | @Default('') @JsonKey(name: 'group_settings_id') String groupId, 34 | @Default(0) @JsonKey(name: 'paid_to_date') double paidToDate, 35 | @Default(0) @JsonKey(name: 'credit_balance') double creditBalance, 36 | @Default(0) @JsonKey(name: 'last_login') int lastLogin, 37 | @Default('') @JsonKey(name: 'size_id') String sizeId, 38 | @Default('') @JsonKey(name: 'public_notes') String publicNotes, 39 | @Default('') String phone, 40 | @Default('') String address1, 41 | @Default('') String address2, 42 | @Default('') String city, 43 | @Default('') String state, 44 | @Default('') @JsonKey(name: 'postal_code') String postalCode, 45 | @Default('') @JsonKey(name: 'country_id') String countryId, 46 | @Default('') @JsonKey(name: 'industry_id') String industryId, 47 | @Default('') @JsonKey(name: 'shipping_address1') String shippingAddress1, 48 | @Default('') @JsonKey(name: 'shipping_address2') String shippingAddress2, 49 | @Default('') @JsonKey(name: 'shipping_city') String shippingCity, 50 | @Default('') @JsonKey(name: 'shipping_state') String shippingState, 51 | @Default('') 52 | @JsonKey(name: 'shipping_postal_code') 53 | String shippingPostalCode, 54 | @Default('') @JsonKey(name: 'shipping_country_id') String shippingCountryId, 55 | ClientSettings? settings, 56 | @Default('') @JsonKey(name: 'id_number') String idNumber, 57 | @Default('') @JsonKey(name: 'vat_number') String vatNumber, 58 | @Default([]) List contacts, 59 | @Default([]) List documents, 60 | }) = _Client; 61 | 62 | /// Create a client using contact details 63 | factory Client.forContact({ 64 | String firstName = '', 65 | String lastName = '', 66 | String email = '', 67 | String phone = '', 68 | }) { 69 | return Client(contacts: [ 70 | ClientContact( 71 | firstName: firstName, 72 | lastName: lastName, 73 | email: email, 74 | phone: phone, 75 | ) 76 | ]); 77 | } 78 | 79 | /// Get the default contact key 80 | String get key => contacts.first.key; 81 | 82 | /// Create a Client from JSON 83 | factory Client.fromJson(Map json) => _$ClientFromJson(json); 84 | } 85 | 86 | /// ClientContact class 87 | @freezed 88 | class ClientContact with _$ClientContact { 89 | /// ClientContact factory constructor 90 | @JsonSerializable(explicitToJson: true) 91 | factory ClientContact({ 92 | @Default('') String id, 93 | @Default('') @JsonKey(name: 'custom_value1') String customValue1, 94 | @Default('') @JsonKey(name: 'custom_value2') String customValue2, 95 | @Default('') @JsonKey(name: 'custom_value3') String customValue3, 96 | @Default('') @JsonKey(name: 'custom_value4') String customValue4, 97 | @Default('') @JsonKey(name: 'first_name') String firstName, 98 | @Default('') @JsonKey(name: 'last_name') String lastName, 99 | @Default('') String email, 100 | @Default(false) @JsonKey(name: 'is_primary') bool isPrimary, 101 | @Default(false) @JsonKey(name: 'is_locked') bool isLocked, 102 | @Default('') String phone, 103 | @Default('') @JsonKey(name: 'contact_key') String key, 104 | @Default(true) @JsonKey(name: 'send_email') bool sendEmail, 105 | @Default(0) @JsonKey(name: 'last_login') int lastLogin, 106 | @Default('') String password, 107 | }) = _ClientContact; 108 | 109 | /// Create a ClientContact from JSON 110 | factory ClientContact.fromJson(Map json) => 111 | _$ClientContactFromJson(json); 112 | } 113 | 114 | /// ClientSettings class 115 | @freezed 116 | class ClientSettings with _$ClientSettings { 117 | /// ClientSettings factory constructor 118 | @JsonSerializable(explicitToJson: true) 119 | factory ClientSettings( 120 | {@Default('') @JsonKey(name: 'currency_id') String? currencyId, 121 | @Default('') @JsonKey(name: 'language_id') String? languageId}) = 122 | _ClientSettings; 123 | 124 | /// Create a ClientSettings from JSON 125 | factory ClientSettings.fromJson(Map json) => 126 | _$ClientSettingsFromJson(json); 127 | } 128 | 129 | /// Multi-item client response 130 | @freezed 131 | class ClientList with _$ClientList { 132 | /// ClientList factory constructor 133 | factory ClientList(List data) = _ClientList; 134 | 135 | /// Create a ClientList from JSON 136 | factory ClientList.fromJson(Map json) => 137 | _$ClientListFromJson(json); 138 | } 139 | 140 | /// Single-item client response 141 | @freezed 142 | class ClientItem with _$ClientItem { 143 | /// ClientItem factory constructor 144 | factory ClientItem(Client data) = _ClientItem; 145 | 146 | /// Create a ClientItem from JSON 147 | factory ClientItem.fromJson(Map json) => 148 | _$ClientItemFromJson(json); 149 | } 150 | -------------------------------------------------------------------------------- /lib/models/client.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'client.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_Client _$$_ClientFromJson(Map json) => _$_Client( 10 | id: json['id'] as String? ?? '', 11 | createdById: json['user_id'] as String? ?? '', 12 | assignedToId: json['assigned_user_id'] as String? ?? '', 13 | createdAt: json['created_at'] as int? ?? 0, 14 | updatedAt: json['updated_at'] as int? ?? 0, 15 | archivedAt: json['archived_at'] as int? ?? 0, 16 | isDeleted: json['is_deleted'] as bool? ?? false, 17 | customValue1: json['custom_value1'] as String? ?? '', 18 | customValue2: json['custom_value2'] as String? ?? '', 19 | customValue3: json['custom_value3'] as String? ?? '', 20 | customValue4: json['custom_value4'] as String? ?? '', 21 | name: json['name'] as String? ?? '', 22 | website: json['website'] as String? ?? '', 23 | privateNotes: json['private_notes'] as String? ?? '', 24 | balance: (json['balance'] as num?)?.toDouble() ?? 0, 25 | groupId: json['group_settings_id'] as String? ?? '', 26 | paidToDate: (json['paid_to_date'] as num?)?.toDouble() ?? 0, 27 | creditBalance: (json['credit_balance'] as num?)?.toDouble() ?? 0, 28 | lastLogin: json['last_login'] as int? ?? 0, 29 | sizeId: json['size_id'] as String? ?? '', 30 | publicNotes: json['public_notes'] as String? ?? '', 31 | phone: json['phone'] as String? ?? '', 32 | address1: json['address1'] as String? ?? '', 33 | address2: json['address2'] as String? ?? '', 34 | city: json['city'] as String? ?? '', 35 | state: json['state'] as String? ?? '', 36 | postalCode: json['postal_code'] as String? ?? '', 37 | countryId: json['country_id'] as String? ?? '', 38 | industryId: json['industry_id'] as String? ?? '', 39 | shippingAddress1: json['shipping_address1'] as String? ?? '', 40 | shippingAddress2: json['shipping_address2'] as String? ?? '', 41 | shippingCity: json['shipping_city'] as String? ?? '', 42 | shippingState: json['shipping_state'] as String? ?? '', 43 | shippingPostalCode: json['shipping_postal_code'] as String? ?? '', 44 | shippingCountryId: json['shipping_country_id'] as String? ?? '', 45 | settings: json['settings'] == null 46 | ? null 47 | : ClientSettings.fromJson(json['settings'] as Map), 48 | idNumber: json['id_number'] as String? ?? '', 49 | vatNumber: json['vat_number'] as String? ?? '', 50 | contacts: (json['contacts'] as List?) 51 | ?.map((e) => ClientContact.fromJson(e as Map)) 52 | .toList() ?? 53 | const [], 54 | documents: (json['documents'] as List?) 55 | ?.map((e) => Document.fromJson(e as Map)) 56 | .toList() ?? 57 | const [], 58 | ); 59 | 60 | Map _$$_ClientToJson(_$_Client instance) => { 61 | 'id': instance.id, 62 | 'user_id': instance.createdById, 63 | 'assigned_user_id': instance.assignedToId, 64 | 'created_at': instance.createdAt, 65 | 'updated_at': instance.updatedAt, 66 | 'archived_at': instance.archivedAt, 67 | 'is_deleted': instance.isDeleted, 68 | 'custom_value1': instance.customValue1, 69 | 'custom_value2': instance.customValue2, 70 | 'custom_value3': instance.customValue3, 71 | 'custom_value4': instance.customValue4, 72 | 'name': instance.name, 73 | 'website': instance.website, 74 | 'private_notes': instance.privateNotes, 75 | 'balance': instance.balance, 76 | 'group_settings_id': instance.groupId, 77 | 'paid_to_date': instance.paidToDate, 78 | 'credit_balance': instance.creditBalance, 79 | 'last_login': instance.lastLogin, 80 | 'size_id': instance.sizeId, 81 | 'public_notes': instance.publicNotes, 82 | 'phone': instance.phone, 83 | 'address1': instance.address1, 84 | 'address2': instance.address2, 85 | 'city': instance.city, 86 | 'state': instance.state, 87 | 'postal_code': instance.postalCode, 88 | 'country_id': instance.countryId, 89 | 'industry_id': instance.industryId, 90 | 'shipping_address1': instance.shippingAddress1, 91 | 'shipping_address2': instance.shippingAddress2, 92 | 'shipping_city': instance.shippingCity, 93 | 'shipping_state': instance.shippingState, 94 | 'shipping_postal_code': instance.shippingPostalCode, 95 | 'shipping_country_id': instance.shippingCountryId, 96 | 'settings': instance.settings?.toJson(), 97 | 'id_number': instance.idNumber, 98 | 'vat_number': instance.vatNumber, 99 | 'contacts': instance.contacts.map((e) => e.toJson()).toList(), 100 | 'documents': instance.documents.map((e) => e.toJson()).toList(), 101 | }; 102 | 103 | _$_ClientContact _$$_ClientContactFromJson(Map json) => 104 | _$_ClientContact( 105 | id: json['id'] as String? ?? '', 106 | customValue1: json['custom_value1'] as String? ?? '', 107 | customValue2: json['custom_value2'] as String? ?? '', 108 | customValue3: json['custom_value3'] as String? ?? '', 109 | customValue4: json['custom_value4'] as String? ?? '', 110 | firstName: json['first_name'] as String? ?? '', 111 | lastName: json['last_name'] as String? ?? '', 112 | email: json['email'] as String? ?? '', 113 | isPrimary: json['is_primary'] as bool? ?? false, 114 | isLocked: json['is_locked'] as bool? ?? false, 115 | phone: json['phone'] as String? ?? '', 116 | key: json['contact_key'] as String? ?? '', 117 | sendEmail: json['send_email'] as bool? ?? true, 118 | lastLogin: json['last_login'] as int? ?? 0, 119 | password: json['password'] as String? ?? '', 120 | ); 121 | 122 | Map _$$_ClientContactToJson(_$_ClientContact instance) => 123 | { 124 | 'id': instance.id, 125 | 'custom_value1': instance.customValue1, 126 | 'custom_value2': instance.customValue2, 127 | 'custom_value3': instance.customValue3, 128 | 'custom_value4': instance.customValue4, 129 | 'first_name': instance.firstName, 130 | 'last_name': instance.lastName, 131 | 'email': instance.email, 132 | 'is_primary': instance.isPrimary, 133 | 'is_locked': instance.isLocked, 134 | 'phone': instance.phone, 135 | 'contact_key': instance.key, 136 | 'send_email': instance.sendEmail, 137 | 'last_login': instance.lastLogin, 138 | 'password': instance.password, 139 | }; 140 | 141 | _$_ClientSettings _$$_ClientSettingsFromJson(Map json) => 142 | _$_ClientSettings( 143 | currencyId: json['currency_id'] as String? ?? '', 144 | languageId: json['language_id'] as String? ?? '', 145 | ); 146 | 147 | Map _$$_ClientSettingsToJson(_$_ClientSettings instance) => 148 | { 149 | 'currency_id': instance.currencyId, 150 | 'language_id': instance.languageId, 151 | }; 152 | 153 | _$_ClientList _$$_ClientListFromJson(Map json) => 154 | _$_ClientList( 155 | (json['data'] as List) 156 | .map((e) => Client.fromJson(e as Map)) 157 | .toList(), 158 | ); 159 | 160 | Map _$$_ClientListToJson(_$_ClientList instance) => 161 | { 162 | 'data': instance.data, 163 | }; 164 | 165 | _$_ClientItem _$$_ClientItemFromJson(Map json) => 166 | _$_ClientItem( 167 | Client.fromJson(json['data'] as Map), 168 | ); 169 | 170 | Map _$$_ClientItemToJson(_$_ClientItem instance) => 171 | { 172 | 'data': instance.data, 173 | }; 174 | -------------------------------------------------------------------------------- /lib/models/company.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:invoiceninja/models/document.dart'; 4 | 5 | part 'company.freezed.dart'; 6 | 7 | part 'company.g.dart'; 8 | 9 | /// Company class 10 | @freezed 11 | abstract class Company implements _$Company { 12 | /// Default constructor 13 | const Company._(); 14 | 15 | /// Company factory constructor 16 | @JsonSerializable(explicitToJson: true) 17 | factory Company({ 18 | @Default('') @JsonKey(name: 'company_key') String key, 19 | CompanySettings? settings, 20 | }) = _Company; 21 | 22 | /// Create a Company from JSON 23 | factory Company.fromJson(Map json) => 24 | _$CompanyFromJson(json); 25 | } 26 | 27 | /// CompanySettings class 28 | @freezed 29 | class CompanySettings with _$CompanySettings { 30 | /// CompanySettings factory constructor 31 | @JsonSerializable(explicitToJson: true) 32 | factory CompanySettings({ 33 | @Default('') String? name, 34 | @Default('') String? address1, 35 | @Default('') String? address2, 36 | @Default('') String? city, 37 | @Default('') String? state, 38 | @Default('') String? phone, 39 | @Default('') String? email, 40 | @Default('') String? website, 41 | @Default('') @JsonKey(name: 'custom_value1') String? customValue1, 42 | @Default('') @JsonKey(name: 'custom_value2') String? customValue2, 43 | @Default('') @JsonKey(name: 'custom_value3') String? customValue3, 44 | @Default('') @JsonKey(name: 'custom_value4') String? customValue4, 45 | @Default('') @JsonKey(name: 'company_logo') String? companyLogo, 46 | @Default('') @JsonKey(name: 'postal_code') String? postalCode, 47 | @Default('') @JsonKey(name: 'country_id') String? countryId, 48 | @Default('') @JsonKey(name: 'vat_number') String? vatNumber, 49 | @Default([]) List documents, 50 | @Default({}) 51 | @JsonKey(name: 'custom_fields') 52 | Map customFields, 53 | }) = _CompanySettings; 54 | 55 | /// Create a CompanySettings from JSON 56 | factory CompanySettings.fromJson(Map json) => 57 | _$CompanySettingsFromJson(json); 58 | } 59 | 60 | /// Multi-item company response 61 | @freezed 62 | class CompanyList with _$CompanyList { 63 | /// CompanyList factory constructor 64 | factory CompanyList(List data) = _CompanyList; 65 | 66 | /// Create a CompanyList from JSON 67 | factory CompanyList.fromJson(Map json) => 68 | _$CompanyListFromJson(json); 69 | } 70 | 71 | /// Single-item company response 72 | @freezed 73 | class CompanyItem with _$CompanyItem { 74 | /// CompanyItem factory constructor 75 | factory CompanyItem(Company data) = _CompanyItem; 76 | 77 | /// Create a CompanyItem from JSON 78 | factory CompanyItem.fromJson(Map json) => 79 | _$CompanyItemFromJson(json); 80 | } 81 | -------------------------------------------------------------------------------- /lib/models/company.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'company.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_Company _$$_CompanyFromJson(Map json) => _$_Company( 10 | key: json['company_key'] as String? ?? '', 11 | settings: json['settings'] == null 12 | ? null 13 | : CompanySettings.fromJson(json['settings'] as Map), 14 | ); 15 | 16 | Map _$$_CompanyToJson(_$_Company instance) => 17 | { 18 | 'company_key': instance.key, 19 | 'settings': instance.settings?.toJson(), 20 | }; 21 | 22 | _$_CompanySettings _$$_CompanySettingsFromJson(Map json) => 23 | _$_CompanySettings( 24 | name: json['name'] as String? ?? '', 25 | address1: json['address1'] as String? ?? '', 26 | address2: json['address2'] as String? ?? '', 27 | city: json['city'] as String? ?? '', 28 | state: json['state'] as String? ?? '', 29 | phone: json['phone'] as String? ?? '', 30 | email: json['email'] as String? ?? '', 31 | website: json['website'] as String? ?? '', 32 | customValue1: json['custom_value1'] as String? ?? '', 33 | customValue2: json['custom_value2'] as String? ?? '', 34 | customValue3: json['custom_value3'] as String? ?? '', 35 | customValue4: json['custom_value4'] as String? ?? '', 36 | companyLogo: json['company_logo'] as String? ?? '', 37 | postalCode: json['postal_code'] as String? ?? '', 38 | countryId: json['country_id'] as String? ?? '', 39 | vatNumber: json['vat_number'] as String? ?? '', 40 | documents: (json['documents'] as List?) 41 | ?.map((e) => Document.fromJson(e as Map)) 42 | .toList() ?? 43 | const [], 44 | customFields: (json['custom_fields'] as Map?)?.map( 45 | (k, e) => MapEntry(k, e as String), 46 | ) ?? 47 | const {}, 48 | ); 49 | 50 | Map _$$_CompanySettingsToJson(_$_CompanySettings instance) => 51 | { 52 | 'name': instance.name, 53 | 'address1': instance.address1, 54 | 'address2': instance.address2, 55 | 'city': instance.city, 56 | 'state': instance.state, 57 | 'phone': instance.phone, 58 | 'email': instance.email, 59 | 'website': instance.website, 60 | 'custom_value1': instance.customValue1, 61 | 'custom_value2': instance.customValue2, 62 | 'custom_value3': instance.customValue3, 63 | 'custom_value4': instance.customValue4, 64 | 'company_logo': instance.companyLogo, 65 | 'postal_code': instance.postalCode, 66 | 'country_id': instance.countryId, 67 | 'vat_number': instance.vatNumber, 68 | 'documents': instance.documents.map((e) => e.toJson()).toList(), 69 | 'custom_fields': instance.customFields, 70 | }; 71 | 72 | _$_CompanyList _$$_CompanyListFromJson(Map json) => 73 | _$_CompanyList( 74 | (json['data'] as List) 75 | .map((e) => Company.fromJson(e as Map)) 76 | .toList(), 77 | ); 78 | 79 | Map _$$_CompanyListToJson(_$_CompanyList instance) => 80 | { 81 | 'data': instance.data, 82 | }; 83 | 84 | _$_CompanyItem _$$_CompanyItemFromJson(Map json) => 85 | _$_CompanyItem( 86 | Company.fromJson(json['data'] as Map), 87 | ); 88 | 89 | Map _$$_CompanyItemToJson(_$_CompanyItem instance) => 90 | { 91 | 'data': instance.data, 92 | }; 93 | -------------------------------------------------------------------------------- /lib/models/credit.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:invoiceninja/models/client.dart'; 4 | import 'package:invoiceninja/models/document.dart'; 5 | import 'package:invoiceninja/models/product.dart'; 6 | 7 | import 'invoice.dart'; 8 | 9 | part 'credit.freezed.dart'; 10 | 11 | part 'credit.g.dart'; 12 | 13 | /// Credit class 14 | @freezed 15 | class Credit with _$Credit { 16 | /// Default constructor 17 | const Credit._(); 18 | 19 | /// Credit factory constructor 20 | @JsonSerializable(explicitToJson: true) 21 | const factory Credit({ 22 | @Default('') String id, 23 | @Default('') @JsonKey(name: 'user_id') String createdById, 24 | @Default('') @JsonKey(name: 'assigned_user_id') String assignedToId, 25 | @Default(0) @JsonKey(name: 'created_at') int createdAt, 26 | @Default(0) @JsonKey(name: 'updated_at') int updatedAt, 27 | @Default(0) @JsonKey(name: 'archived_at') int archivedAt, 28 | @Default(false) @JsonKey(name: 'is_deleted') bool isDeleted, 29 | @Default('') @JsonKey(name: 'custom_value1') String customValue1, 30 | @Default('') @JsonKey(name: 'custom_value2') String customValue2, 31 | @Default('') @JsonKey(name: 'custom_value3') String customValue3, 32 | @Default('') @JsonKey(name: 'custom_value4') String customValue4, 33 | @Default('') @JsonKey(name: 'client_id') String clientId, 34 | @Default([]) 35 | @JsonKey(name: 'line_items') 36 | List lineItems, 37 | @Default([]) List invitations, 38 | @Default(0) double amount, 39 | @Default(0) double balance, 40 | @Default('') @JsonKey(name: 'status_id') String statusId, 41 | @Default('') @JsonKey(name: 'design_id') String designId, 42 | @Default('') String number, 43 | @Default(0) double discount, 44 | @Default('') @JsonKey(name: 'po_number') String poNumber, 45 | @Default('') String date, 46 | @Default('') @JsonKey(name: 'last_sent_date') String lastSentDate, 47 | @Default('') @JsonKey(name: 'next_send_date') String nextSendDate, 48 | @Default('') String terms, 49 | @Default('') @JsonKey(name: 'public_notes') String publicNotes, 50 | @Default('') @JsonKey(name: 'private_notes') String privateNotes, 51 | @Default(false) 52 | @JsonKey(name: 'uses_inclusive_taxes') 53 | bool usesInclusiveTaxes, 54 | @Default('') @JsonKey(name: 'tax_name1') String taxName1, 55 | @Default(0) @JsonKey(name: 'tax_rate1') double taxRate1, 56 | @Default('') @JsonKey(name: 'tax_name2') String taxName2, 57 | @Default(0) @JsonKey(name: 'tax_rate2') double taxRate2, 58 | @Default('') @JsonKey(name: 'tax_name3') String taxName3, 59 | @Default(0) @JsonKey(name: 'tax_rate3') double taxRate3, 60 | @Default(0) @JsonKey(name: 'total_taxes') double totalTaxes, 61 | @Default(false) @JsonKey(name: 'is_amount_discount') bool isAmountDiscount, 62 | @Default('') String footer, 63 | @Default(0) double partial, 64 | @Default('') @JsonKey(name: 'partial_due_date') String partialDueDate, 65 | @Default(false) @JsonKey(name: 'has_tasks') bool hasTasks, 66 | @Default(false) @JsonKey(name: 'has_expenses') bool hasExpenses, 67 | @Default(0) @JsonKey(name: 'custom_surcharge1') double customSurcharge1, 68 | @Default(0) @JsonKey(name: 'custom_surcharge2') double customSurcharge2, 69 | @Default(0) @JsonKey(name: 'custom_surcharge3') double customSurcharge3, 70 | @Default(0) @JsonKey(name: 'custom_surcharge4') double customSurcharge4, 71 | @Default(false) 72 | @JsonKey(name: 'custom_surcharge_tax1') 73 | bool customSurchargeTax1, 74 | @Default(false) 75 | @JsonKey(name: 'custom_surcharge_tax2') 76 | bool customSurchargeTax2, 77 | @Default(false) 78 | @JsonKey(name: 'custom_surcharge_tax3') 79 | bool customSurchargeTax3, 80 | @Default(false) 81 | @JsonKey(name: 'custom_surcharge_tax4') 82 | bool customSurchargeTax4, 83 | @Default([]) List documents, 84 | @Default('') @JsonKey(name: 'vendor_id') String vendorId, 85 | }) = _Credit; 86 | 87 | /// Get the default invitation URL 88 | String get url => invitations.first.url; 89 | 90 | /// Create an invoice for a client 91 | factory Credit.forClient(Client client, {required List products}) { 92 | return Credit( 93 | clientId: client.id, 94 | lineItems: (products).map((product) => product.toLineItem).toList(), 95 | ); 96 | } 97 | 98 | /// Get the default invitation PDF URL 99 | String get pdfUrl => invitations.first.pdfUrl; 100 | 101 | /// Get the default invitation key 102 | String get key => invitations.first.key; 103 | 104 | /// Determine if the credit is paid 105 | bool get isPaid => statusId == '4'; 106 | 107 | /// Create an Credit from JSON 108 | factory Credit.fromJson(Map json) => _$CreditFromJson(json); 109 | } 110 | 111 | /// Multi-item credit response 112 | @freezed 113 | class CreditList with _$CreditList { 114 | /// CreditList factory constructor 115 | factory CreditList({required List data}) = _CreditList; 116 | 117 | /// Create an CreditList from JSON 118 | factory CreditList.fromJson(Map json) => 119 | _$CreditListFromJson(json); 120 | } 121 | 122 | /// Single-item credit response 123 | @freezed 124 | class CreditItem with _$CreditItem { 125 | /// CreditItem factory constructor 126 | factory CreditItem(Credit data) = _CreditItem; 127 | 128 | /// Create an CreditItem from JSON 129 | factory CreditItem.fromJson(Map json) => 130 | _$CreditItemFromJson(json); 131 | } 132 | 133 | enum CreditAction { 134 | sendEmail, 135 | markSent, 136 | } 137 | -------------------------------------------------------------------------------- /lib/models/credit.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'credit.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_Credit _$$_CreditFromJson(Map json) => _$_Credit( 10 | id: json['id'] as String? ?? '', 11 | createdById: json['user_id'] as String? ?? '', 12 | assignedToId: json['assigned_user_id'] as String? ?? '', 13 | createdAt: json['created_at'] as int? ?? 0, 14 | updatedAt: json['updated_at'] as int? ?? 0, 15 | archivedAt: json['archived_at'] as int? ?? 0, 16 | isDeleted: json['is_deleted'] as bool? ?? false, 17 | customValue1: json['custom_value1'] as String? ?? '', 18 | customValue2: json['custom_value2'] as String? ?? '', 19 | customValue3: json['custom_value3'] as String? ?? '', 20 | customValue4: json['custom_value4'] as String? ?? '', 21 | clientId: json['client_id'] as String? ?? '', 22 | lineItems: (json['line_items'] as List?) 23 | ?.map((e) => InvoiceLineItem.fromJson(e as Map)) 24 | .toList() ?? 25 | const [], 26 | invitations: (json['invitations'] as List?) 27 | ?.map( 28 | (e) => InvoiceInvitation.fromJson(e as Map)) 29 | .toList() ?? 30 | const [], 31 | amount: (json['amount'] as num?)?.toDouble() ?? 0, 32 | balance: (json['balance'] as num?)?.toDouble() ?? 0, 33 | statusId: json['status_id'] as String? ?? '', 34 | designId: json['design_id'] as String? ?? '', 35 | number: json['number'] as String? ?? '', 36 | discount: (json['discount'] as num?)?.toDouble() ?? 0, 37 | poNumber: json['po_number'] as String? ?? '', 38 | date: json['date'] as String? ?? '', 39 | lastSentDate: json['last_sent_date'] as String? ?? '', 40 | nextSendDate: json['next_send_date'] as String? ?? '', 41 | terms: json['terms'] as String? ?? '', 42 | publicNotes: json['public_notes'] as String? ?? '', 43 | privateNotes: json['private_notes'] as String? ?? '', 44 | usesInclusiveTaxes: json['uses_inclusive_taxes'] as bool? ?? false, 45 | taxName1: json['tax_name1'] as String? ?? '', 46 | taxRate1: (json['tax_rate1'] as num?)?.toDouble() ?? 0, 47 | taxName2: json['tax_name2'] as String? ?? '', 48 | taxRate2: (json['tax_rate2'] as num?)?.toDouble() ?? 0, 49 | taxName3: json['tax_name3'] as String? ?? '', 50 | taxRate3: (json['tax_rate3'] as num?)?.toDouble() ?? 0, 51 | totalTaxes: (json['total_taxes'] as num?)?.toDouble() ?? 0, 52 | isAmountDiscount: json['is_amount_discount'] as bool? ?? false, 53 | footer: json['footer'] as String? ?? '', 54 | partial: (json['partial'] as num?)?.toDouble() ?? 0, 55 | partialDueDate: json['partial_due_date'] as String? ?? '', 56 | hasTasks: json['has_tasks'] as bool? ?? false, 57 | hasExpenses: json['has_expenses'] as bool? ?? false, 58 | customSurcharge1: (json['custom_surcharge1'] as num?)?.toDouble() ?? 0, 59 | customSurcharge2: (json['custom_surcharge2'] as num?)?.toDouble() ?? 0, 60 | customSurcharge3: (json['custom_surcharge3'] as num?)?.toDouble() ?? 0, 61 | customSurcharge4: (json['custom_surcharge4'] as num?)?.toDouble() ?? 0, 62 | customSurchargeTax1: json['custom_surcharge_tax1'] as bool? ?? false, 63 | customSurchargeTax2: json['custom_surcharge_tax2'] as bool? ?? false, 64 | customSurchargeTax3: json['custom_surcharge_tax3'] as bool? ?? false, 65 | customSurchargeTax4: json['custom_surcharge_tax4'] as bool? ?? false, 66 | documents: (json['documents'] as List?) 67 | ?.map((e) => Document.fromJson(e as Map)) 68 | .toList() ?? 69 | const [], 70 | vendorId: json['vendor_id'] as String? ?? '', 71 | ); 72 | 73 | Map _$$_CreditToJson(_$_Credit instance) => { 74 | 'id': instance.id, 75 | 'user_id': instance.createdById, 76 | 'assigned_user_id': instance.assignedToId, 77 | 'created_at': instance.createdAt, 78 | 'updated_at': instance.updatedAt, 79 | 'archived_at': instance.archivedAt, 80 | 'is_deleted': instance.isDeleted, 81 | 'custom_value1': instance.customValue1, 82 | 'custom_value2': instance.customValue2, 83 | 'custom_value3': instance.customValue3, 84 | 'custom_value4': instance.customValue4, 85 | 'client_id': instance.clientId, 86 | 'line_items': instance.lineItems.map((e) => e.toJson()).toList(), 87 | 'invitations': instance.invitations.map((e) => e.toJson()).toList(), 88 | 'amount': instance.amount, 89 | 'balance': instance.balance, 90 | 'status_id': instance.statusId, 91 | 'design_id': instance.designId, 92 | 'number': instance.number, 93 | 'discount': instance.discount, 94 | 'po_number': instance.poNumber, 95 | 'date': instance.date, 96 | 'last_sent_date': instance.lastSentDate, 97 | 'next_send_date': instance.nextSendDate, 98 | 'terms': instance.terms, 99 | 'public_notes': instance.publicNotes, 100 | 'private_notes': instance.privateNotes, 101 | 'uses_inclusive_taxes': instance.usesInclusiveTaxes, 102 | 'tax_name1': instance.taxName1, 103 | 'tax_rate1': instance.taxRate1, 104 | 'tax_name2': instance.taxName2, 105 | 'tax_rate2': instance.taxRate2, 106 | 'tax_name3': instance.taxName3, 107 | 'tax_rate3': instance.taxRate3, 108 | 'total_taxes': instance.totalTaxes, 109 | 'is_amount_discount': instance.isAmountDiscount, 110 | 'footer': instance.footer, 111 | 'partial': instance.partial, 112 | 'partial_due_date': instance.partialDueDate, 113 | 'has_tasks': instance.hasTasks, 114 | 'has_expenses': instance.hasExpenses, 115 | 'custom_surcharge1': instance.customSurcharge1, 116 | 'custom_surcharge2': instance.customSurcharge2, 117 | 'custom_surcharge3': instance.customSurcharge3, 118 | 'custom_surcharge4': instance.customSurcharge4, 119 | 'custom_surcharge_tax1': instance.customSurchargeTax1, 120 | 'custom_surcharge_tax2': instance.customSurchargeTax2, 121 | 'custom_surcharge_tax3': instance.customSurchargeTax3, 122 | 'custom_surcharge_tax4': instance.customSurchargeTax4, 123 | 'documents': instance.documents.map((e) => e.toJson()).toList(), 124 | 'vendor_id': instance.vendorId, 125 | }; 126 | 127 | _$_CreditList _$$_CreditListFromJson(Map json) => 128 | _$_CreditList( 129 | data: (json['data'] as List) 130 | .map((e) => Credit.fromJson(e as Map)) 131 | .toList(), 132 | ); 133 | 134 | Map _$$_CreditListToJson(_$_CreditList instance) => 135 | { 136 | 'data': instance.data, 137 | }; 138 | 139 | _$_CreditItem _$$_CreditItemFromJson(Map json) => 140 | _$_CreditItem( 141 | Credit.fromJson(json['data'] as Map), 142 | ); 143 | 144 | Map _$$_CreditItemToJson(_$_CreditItem instance) => 145 | { 146 | 'data': instance.data, 147 | }; 148 | -------------------------------------------------------------------------------- /lib/models/document.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | 4 | part 'document.freezed.dart'; 5 | 6 | part 'document.g.dart'; 7 | 8 | /// Document class 9 | @freezed 10 | abstract class Document implements _$Document { 11 | /// Default constructor 12 | const Document._(); 13 | 14 | /// Document factory constructor 15 | @JsonSerializable(explicitToJson: true) 16 | factory Document({ 17 | @Default('') String id, 18 | @Default('') String name, 19 | @Default('') String type, 20 | @Default('') String url, 21 | @Default(0) int width, 22 | @Default(0) int height, 23 | @Default(0) int size, 24 | @Default('') String preview, 25 | @Default('') String disk, 26 | @Default('') String hash, 27 | @Default('') @JsonKey(name: 'user_id') String createdById, 28 | @Default('') @JsonKey(name: 'assigned_user_id') String assignedToId, 29 | @Default(0) @JsonKey(name: 'created_at') int createdAt, 30 | @Default(0) @JsonKey(name: 'updated_at') int updatedAt, 31 | @Default(0) @JsonKey(name: 'archived_at') int archivedAt, 32 | @Default(false) @JsonKey(name: 'is_deleted') bool isDeleted, 33 | @Default(false) @JsonKey(name: 'is_default') bool isDefault, 34 | }) = _Document; 35 | 36 | /// Create an Document from JSON 37 | factory Document.fromJson(Map json) => 38 | _$DocumentFromJson(json); 39 | } 40 | 41 | /// Multi-item document response 42 | @freezed 43 | class DocumentList with _$DocumentList { 44 | /// DocumentList factory constructor 45 | factory DocumentList({required List data}) = _DocumentList; 46 | 47 | /// Create an DocumentList from JSON 48 | factory DocumentList.fromJson(Map json) => 49 | _$DocumentListFromJson(json); 50 | } 51 | 52 | /// Single-item document response 53 | @freezed 54 | class DocumentItem with _$DocumentItem { 55 | /// DocumentItem factory constructor 56 | factory DocumentItem(Document data) = _DocumentItem; 57 | 58 | /// Create an DocumentItem from JSON 59 | factory DocumentItem.fromJson(Map json) => 60 | _$DocumentItemFromJson(json); 61 | } 62 | -------------------------------------------------------------------------------- /lib/models/document.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'document.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_Document _$$_DocumentFromJson(Map json) => _$_Document( 10 | id: json['id'] as String? ?? '', 11 | name: json['name'] as String? ?? '', 12 | type: json['type'] as String? ?? '', 13 | url: json['url'] as String? ?? '', 14 | width: json['width'] as int? ?? 0, 15 | height: json['height'] as int? ?? 0, 16 | size: json['size'] as int? ?? 0, 17 | preview: json['preview'] as String? ?? '', 18 | disk: json['disk'] as String? ?? '', 19 | hash: json['hash'] as String? ?? '', 20 | createdById: json['user_id'] as String? ?? '', 21 | assignedToId: json['assigned_user_id'] as String? ?? '', 22 | createdAt: json['created_at'] as int? ?? 0, 23 | updatedAt: json['updated_at'] as int? ?? 0, 24 | archivedAt: json['archived_at'] as int? ?? 0, 25 | isDeleted: json['is_deleted'] as bool? ?? false, 26 | isDefault: json['is_default'] as bool? ?? false, 27 | ); 28 | 29 | Map _$$_DocumentToJson(_$_Document instance) => 30 | { 31 | 'id': instance.id, 32 | 'name': instance.name, 33 | 'type': instance.type, 34 | 'url': instance.url, 35 | 'width': instance.width, 36 | 'height': instance.height, 37 | 'size': instance.size, 38 | 'preview': instance.preview, 39 | 'disk': instance.disk, 40 | 'hash': instance.hash, 41 | 'user_id': instance.createdById, 42 | 'assigned_user_id': instance.assignedToId, 43 | 'created_at': instance.createdAt, 44 | 'updated_at': instance.updatedAt, 45 | 'archived_at': instance.archivedAt, 46 | 'is_deleted': instance.isDeleted, 47 | 'is_default': instance.isDefault, 48 | }; 49 | 50 | _$_DocumentList _$$_DocumentListFromJson(Map json) => 51 | _$_DocumentList( 52 | data: (json['data'] as List) 53 | .map((e) => Document.fromJson(e as Map)) 54 | .toList(), 55 | ); 56 | 57 | Map _$$_DocumentListToJson(_$_DocumentList instance) => 58 | { 59 | 'data': instance.data, 60 | }; 61 | 62 | _$_DocumentItem _$$_DocumentItemFromJson(Map json) => 63 | _$_DocumentItem( 64 | Document.fromJson(json['data'] as Map), 65 | ); 66 | 67 | Map _$$_DocumentItemToJson(_$_DocumentItem instance) => 68 | { 69 | 'data': instance.data, 70 | }; 71 | -------------------------------------------------------------------------------- /lib/models/payment.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:invoiceninja/models/document.dart'; 4 | 5 | part 'payment.freezed.dart'; 6 | 7 | part 'payment.g.dart'; 8 | 9 | /// Payment class 10 | @freezed 11 | abstract class Payment implements _$Payment { 12 | /// Default constructor 13 | const Payment._(); 14 | 15 | /// Payment factory constructor 16 | @JsonSerializable(explicitToJson: true) 17 | factory Payment({ 18 | @Default('') String id, 19 | @Default('') @JsonKey(name: 'user_id') String createdById, 20 | @Default('') @JsonKey(name: 'assigned_user_id') String assignedToId, 21 | @Default(0) @JsonKey(name: 'created_at') int createdAt, 22 | @Default(0) @JsonKey(name: 'updated_at') int updatedAt, 23 | @Default(0) @JsonKey(name: 'archived_at') int archivedAt, 24 | @Default(false) @JsonKey(name: 'is_deleted') bool isDeleted, 25 | @Default('') @JsonKey(name: 'custom_value1') String customValue1, 26 | @Default('') @JsonKey(name: 'custom_value2') String customValue2, 27 | @Default('') @JsonKey(name: 'custom_value3') String customValue3, 28 | @Default('') @JsonKey(name: 'custom_value4') String customValue4, 29 | @Default('') @JsonKey(name: 'client_id') String clientId, 30 | @Default('') @JsonKey(name: 'invitation_id') String invitationId, 31 | @Default('') @JsonKey(name: 'client_contact_id') String clientContactId, 32 | @Default('') @JsonKey(name: 'type_id') String typeId, 33 | @Default('') @JsonKey(name: 'date') String date, 34 | @Default('') 35 | @JsonKey(name: 'transaction_reference') 36 | String transactionReference, 37 | @Default('') @JsonKey(name: 'private_notes') String privateNotes, 38 | @Default(false) @JsonKey(name: 'is_manual') bool isManual, 39 | @Default(0.0) @JsonKey(name: 'amount') double amount, 40 | @Default(0.0) @JsonKey(name: 'applied') double applied, 41 | @Default(0.0) @JsonKey(name: 'refunded') double refunded, 42 | @Default(0.0) @JsonKey(name: 'company_gateway_id') double companyGatewayId, 43 | @Default([]) 44 | @JsonKey(name: 'paymentables') 45 | List paymentables, 46 | @Default([]) 47 | @JsonKey(name: 'invoices') 48 | List invoices, 49 | @Default([]) 50 | @JsonKey(name: 'credits') 51 | List credits, 52 | @Default([]) List documents, 53 | }) = _Payment; 54 | 55 | /// Create an Payment from JSON 56 | factory Payment.fromJson(Map json) => 57 | _$PaymentFromJson(json); 58 | } 59 | 60 | /// Paymentable class 61 | @freezed 62 | abstract class Paymentable implements _$Paymentable { 63 | /// Paymentable factory constructor 64 | @JsonSerializable(explicitToJson: true) 65 | factory Paymentable({ 66 | @Default('') String id, 67 | @Default('') @JsonKey(name: 'invoice_id') String invoiceId, 68 | @Default('') @JsonKey(name: 'credit_id') String creditId, 69 | @Default(0) double amount, 70 | @Default(0) double refunded, 71 | @Default(0) @JsonKey(name: 'created_at') int createdAt, 72 | @Default(0) @JsonKey(name: 'updated_at') int updatedAt, 73 | }) = _Paymentable; 74 | 75 | /// Create an Paymentable from JSON 76 | factory Paymentable.fromJson(Map json) => 77 | _$PaymentableFromJson(json); 78 | } 79 | 80 | /// Multi-item payment response 81 | @freezed 82 | class PaymentList with _$PaymentList { 83 | /// PaymentList factory constructor 84 | factory PaymentList({required List data}) = _PaymentList; 85 | 86 | /// Create an PaymentList from JSON 87 | factory PaymentList.fromJson(Map json) => 88 | _$PaymentListFromJson(json); 89 | } 90 | 91 | /// Single-item payment response 92 | @freezed 93 | class PaymentItem with _$PaymentItem { 94 | /// PaymentItem factory constructor 95 | factory PaymentItem(Payment data) = _PaymentItem; 96 | 97 | /// Create an PaymentItem from JSON 98 | factory PaymentItem.fromJson(Map json) => 99 | _$PaymentItemFromJson(json); 100 | } 101 | 102 | enum PaymentAction { 103 | sendEmail, 104 | } 105 | -------------------------------------------------------------------------------- /lib/models/payment.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'payment.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_Payment _$$_PaymentFromJson(Map json) => _$_Payment( 10 | id: json['id'] as String? ?? '', 11 | createdById: json['user_id'] as String? ?? '', 12 | assignedToId: json['assigned_user_id'] as String? ?? '', 13 | createdAt: json['created_at'] as int? ?? 0, 14 | updatedAt: json['updated_at'] as int? ?? 0, 15 | archivedAt: json['archived_at'] as int? ?? 0, 16 | isDeleted: json['is_deleted'] as bool? ?? false, 17 | customValue1: json['custom_value1'] as String? ?? '', 18 | customValue2: json['custom_value2'] as String? ?? '', 19 | customValue3: json['custom_value3'] as String? ?? '', 20 | customValue4: json['custom_value4'] as String? ?? '', 21 | clientId: json['client_id'] as String? ?? '', 22 | invitationId: json['invitation_id'] as String? ?? '', 23 | clientContactId: json['client_contact_id'] as String? ?? '', 24 | typeId: json['type_id'] as String? ?? '', 25 | date: json['date'] as String? ?? '', 26 | transactionReference: json['transaction_reference'] as String? ?? '', 27 | privateNotes: json['private_notes'] as String? ?? '', 28 | isManual: json['is_manual'] as bool? ?? false, 29 | amount: (json['amount'] as num?)?.toDouble() ?? 0.0, 30 | applied: (json['applied'] as num?)?.toDouble() ?? 0.0, 31 | refunded: (json['refunded'] as num?)?.toDouble() ?? 0.0, 32 | companyGatewayId: (json['company_gateway_id'] as num?)?.toDouble() ?? 0.0, 33 | paymentables: (json['paymentables'] as List?) 34 | ?.map((e) => Paymentable.fromJson(e as Map)) 35 | .toList() ?? 36 | const [], 37 | invoices: (json['invoices'] as List?) 38 | ?.map((e) => Paymentable.fromJson(e as Map)) 39 | .toList() ?? 40 | const [], 41 | credits: (json['credits'] as List?) 42 | ?.map((e) => Paymentable.fromJson(e as Map)) 43 | .toList() ?? 44 | const [], 45 | documents: (json['documents'] as List?) 46 | ?.map((e) => Document.fromJson(e as Map)) 47 | .toList() ?? 48 | const [], 49 | ); 50 | 51 | Map _$$_PaymentToJson(_$_Payment instance) => 52 | { 53 | 'id': instance.id, 54 | 'user_id': instance.createdById, 55 | 'assigned_user_id': instance.assignedToId, 56 | 'created_at': instance.createdAt, 57 | 'updated_at': instance.updatedAt, 58 | 'archived_at': instance.archivedAt, 59 | 'is_deleted': instance.isDeleted, 60 | 'custom_value1': instance.customValue1, 61 | 'custom_value2': instance.customValue2, 62 | 'custom_value3': instance.customValue3, 63 | 'custom_value4': instance.customValue4, 64 | 'client_id': instance.clientId, 65 | 'invitation_id': instance.invitationId, 66 | 'client_contact_id': instance.clientContactId, 67 | 'type_id': instance.typeId, 68 | 'date': instance.date, 69 | 'transaction_reference': instance.transactionReference, 70 | 'private_notes': instance.privateNotes, 71 | 'is_manual': instance.isManual, 72 | 'amount': instance.amount, 73 | 'applied': instance.applied, 74 | 'refunded': instance.refunded, 75 | 'company_gateway_id': instance.companyGatewayId, 76 | 'paymentables': instance.paymentables.map((e) => e.toJson()).toList(), 77 | 'invoices': instance.invoices.map((e) => e.toJson()).toList(), 78 | 'credits': instance.credits.map((e) => e.toJson()).toList(), 79 | 'documents': instance.documents.map((e) => e.toJson()).toList(), 80 | }; 81 | 82 | _$_Paymentable _$$_PaymentableFromJson(Map json) => 83 | _$_Paymentable( 84 | id: json['id'] as String? ?? '', 85 | invoiceId: json['invoice_id'] as String? ?? '', 86 | creditId: json['credit_id'] as String? ?? '', 87 | amount: (json['amount'] as num?)?.toDouble() ?? 0, 88 | refunded: (json['refunded'] as num?)?.toDouble() ?? 0, 89 | createdAt: json['created_at'] as int? ?? 0, 90 | updatedAt: json['updated_at'] as int? ?? 0, 91 | ); 92 | 93 | Map _$$_PaymentableToJson(_$_Paymentable instance) => 94 | { 95 | 'id': instance.id, 96 | 'invoice_id': instance.invoiceId, 97 | 'credit_id': instance.creditId, 98 | 'amount': instance.amount, 99 | 'refunded': instance.refunded, 100 | 'created_at': instance.createdAt, 101 | 'updated_at': instance.updatedAt, 102 | }; 103 | 104 | _$_PaymentList _$$_PaymentListFromJson(Map json) => 105 | _$_PaymentList( 106 | data: (json['data'] as List) 107 | .map((e) => Payment.fromJson(e as Map)) 108 | .toList(), 109 | ); 110 | 111 | Map _$$_PaymentListToJson(_$_PaymentList instance) => 112 | { 113 | 'data': instance.data, 114 | }; 115 | 116 | _$_PaymentItem _$$_PaymentItemFromJson(Map json) => 117 | _$_PaymentItem( 118 | Payment.fromJson(json['data'] as Map), 119 | ); 120 | 121 | Map _$$_PaymentItemToJson(_$_PaymentItem instance) => 122 | { 123 | 'data': instance.data, 124 | }; 125 | -------------------------------------------------------------------------------- /lib/models/product.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:invoiceninja/models/document.dart'; 4 | import 'package:invoiceninja/models/invoice.dart'; 5 | 6 | part 'product.freezed.dart'; 7 | 8 | part 'product.g.dart'; 9 | 10 | /// Product class 11 | @freezed 12 | class Product with _$Product { 13 | /// Default constructor 14 | const Product._(); 15 | 16 | /// Product factory constructor 17 | @JsonSerializable(explicitToJson: true) 18 | const factory Product({ 19 | @Default('') String id, 20 | @Default('') @JsonKey(name: 'user_id') String createdById, 21 | @Default('') @JsonKey(name: 'assigned_user_id') String assignedToId, 22 | @Default(0) @JsonKey(name: 'created_at') int createdAt, 23 | @Default(0) @JsonKey(name: 'updated_at') int updatedAt, 24 | @Default(0) @JsonKey(name: 'archived_at') int archivedAt, 25 | @Default(false) @JsonKey(name: 'is_deleted') bool isDeleted, 26 | @Default('') @JsonKey(name: 'custom_value1') String customValue1, 27 | @Default('') @JsonKey(name: 'custom_value2') String customValue2, 28 | @Default('') @JsonKey(name: 'custom_value3') String customValue3, 29 | @Default('') @JsonKey(name: 'custom_value4') String customValue4, 30 | @Default('') @JsonKey(name: 'product_key') String productKey, 31 | @Default('') String notes, 32 | @Default(0) double cost, 33 | @Default(0) double price, 34 | @Default(0) double quantity, 35 | @Default('') @JsonKey(name: 'tax_name1') String taxName1, 36 | @Default(0) @JsonKey(name: 'tax_rate1') double taxRate1, 37 | @Default('') @JsonKey(name: 'tax_name2') String taxName2, 38 | @Default(0) @JsonKey(name: 'tax_rate2') double taxRate2, 39 | @Default('') @JsonKey(name: 'tax_name3') String taxName3, 40 | @Default(0) @JsonKey(name: 'tax_rate3') double taxRate3, 41 | @Default([]) List documents, 42 | }) = _Product; 43 | 44 | /// Convert a product to an invoice line item 45 | InvoiceLineItem get toLineItem => InvoiceLineItem( 46 | productKey: productKey, 47 | notes: notes, 48 | cost: price, 49 | quantity: quantity, 50 | customValue1: customValue1, 51 | customValue2: customValue2, 52 | customValue3: customValue3, 53 | customValue4: customValue4, 54 | taxName1: taxName1, 55 | taxRate1: taxRate1, 56 | taxName2: taxName2, 57 | taxRate2: taxRate2, 58 | taxName3: taxName3, 59 | taxRate3: taxRate3, 60 | ); 61 | 62 | /// Create a Product from JSON 63 | factory Product.fromJson(Map json) => 64 | _$ProductFromJson(json); 65 | } 66 | 67 | /// Multi-item product response 68 | @freezed 69 | class ProductList with _$ProductList { 70 | /// ProductList factory constructor 71 | factory ProductList({required List data}) = _ProductList; 72 | 73 | /// Create a ProductList from JSON 74 | factory ProductList.fromJson(Map json) => 75 | _$ProductListFromJson(json); 76 | } 77 | 78 | /// Single-item product response 79 | @freezed 80 | class ProductItem with _$ProductItem { 81 | /// ProductItem factory constructor 82 | factory ProductItem(Product data) = _ProductItem; 83 | 84 | /// Create a ProductItem from JSON 85 | factory ProductItem.fromJson(Map json) => 86 | _$ProductItemFromJson(json); 87 | } 88 | -------------------------------------------------------------------------------- /lib/models/product.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'product.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_Product _$$_ProductFromJson(Map json) => _$_Product( 10 | id: json['id'] as String? ?? '', 11 | createdById: json['user_id'] as String? ?? '', 12 | assignedToId: json['assigned_user_id'] as String? ?? '', 13 | createdAt: json['created_at'] as int? ?? 0, 14 | updatedAt: json['updated_at'] as int? ?? 0, 15 | archivedAt: json['archived_at'] as int? ?? 0, 16 | isDeleted: json['is_deleted'] as bool? ?? false, 17 | customValue1: json['custom_value1'] as String? ?? '', 18 | customValue2: json['custom_value2'] as String? ?? '', 19 | customValue3: json['custom_value3'] as String? ?? '', 20 | customValue4: json['custom_value4'] as String? ?? '', 21 | productKey: json['product_key'] as String? ?? '', 22 | notes: json['notes'] as String? ?? '', 23 | cost: (json['cost'] as num?)?.toDouble() ?? 0, 24 | price: (json['price'] as num?)?.toDouble() ?? 0, 25 | quantity: (json['quantity'] as num?)?.toDouble() ?? 0, 26 | taxName1: json['tax_name1'] as String? ?? '', 27 | taxRate1: (json['tax_rate1'] as num?)?.toDouble() ?? 0, 28 | taxName2: json['tax_name2'] as String? ?? '', 29 | taxRate2: (json['tax_rate2'] as num?)?.toDouble() ?? 0, 30 | taxName3: json['tax_name3'] as String? ?? '', 31 | taxRate3: (json['tax_rate3'] as num?)?.toDouble() ?? 0, 32 | documents: (json['documents'] as List?) 33 | ?.map((e) => Document.fromJson(e as Map)) 34 | .toList() ?? 35 | const [], 36 | ); 37 | 38 | Map _$$_ProductToJson(_$_Product instance) => 39 | { 40 | 'id': instance.id, 41 | 'user_id': instance.createdById, 42 | 'assigned_user_id': instance.assignedToId, 43 | 'created_at': instance.createdAt, 44 | 'updated_at': instance.updatedAt, 45 | 'archived_at': instance.archivedAt, 46 | 'is_deleted': instance.isDeleted, 47 | 'custom_value1': instance.customValue1, 48 | 'custom_value2': instance.customValue2, 49 | 'custom_value3': instance.customValue3, 50 | 'custom_value4': instance.customValue4, 51 | 'product_key': instance.productKey, 52 | 'notes': instance.notes, 53 | 'cost': instance.cost, 54 | 'price': instance.price, 55 | 'quantity': instance.quantity, 56 | 'tax_name1': instance.taxName1, 57 | 'tax_rate1': instance.taxRate1, 58 | 'tax_name2': instance.taxName2, 59 | 'tax_rate2': instance.taxRate2, 60 | 'tax_name3': instance.taxName3, 61 | 'tax_rate3': instance.taxRate3, 62 | 'documents': instance.documents.map((e) => e.toJson()).toList(), 63 | }; 64 | 65 | _$_ProductList _$$_ProductListFromJson(Map json) => 66 | _$_ProductList( 67 | data: (json['data'] as List) 68 | .map((e) => Product.fromJson(e as Map)) 69 | .toList(), 70 | ); 71 | 72 | Map _$$_ProductListToJson(_$_ProductList instance) => 73 | { 74 | 'data': instance.data, 75 | }; 76 | 77 | _$_ProductItem _$$_ProductItemFromJson(Map json) => 78 | _$_ProductItem( 79 | Product.fromJson(json['data'] as Map), 80 | ); 81 | 82 | Map _$$_ProductItemToJson(_$_ProductItem instance) => 83 | { 84 | 'data': instance.data, 85 | }; 86 | -------------------------------------------------------------------------------- /lib/models/quote.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:invoiceninja/models/client.dart'; 4 | import 'package:invoiceninja/models/document.dart'; 5 | import 'package:invoiceninja/models/invoice.dart'; 6 | import 'package:invoiceninja/models/product.dart'; 7 | 8 | part 'quote.freezed.dart'; 9 | 10 | part 'quote.g.dart'; 11 | 12 | /// Quote class 13 | @freezed 14 | class Quote with _$Quote { 15 | /// Default constructor 16 | const Quote._(); 17 | 18 | /// Quote factory constructor 19 | @JsonSerializable(explicitToJson: true) 20 | const factory Quote({ 21 | @Default('') String id, 22 | @Default('') @JsonKey(name: 'user_id') String createdById, 23 | @Default('') @JsonKey(name: 'assigned_user_id') String assignedToId, 24 | @Default(0) @JsonKey(name: 'created_at') int createdAt, 25 | @Default(0) @JsonKey(name: 'updated_at') int updatedAt, 26 | @Default(0) @JsonKey(name: 'archived_at') int archivedAt, 27 | @Default(false) @JsonKey(name: 'is_deleted') bool isDeleted, 28 | @Default('') @JsonKey(name: 'custom_value1') String customValue1, 29 | @Default('') @JsonKey(name: 'custom_value2') String customValue2, 30 | @Default('') @JsonKey(name: 'custom_value3') String customValue3, 31 | @Default('') @JsonKey(name: 'custom_value4') String customValue4, 32 | @Default('') @JsonKey(name: 'client_id') String clientId, 33 | @Default([]) 34 | @JsonKey(name: 'line_items') 35 | List lineItems, 36 | @Default([]) List invitations, 37 | @Default(0) double amount, 38 | @Default(0) double balance, 39 | @Default('') @JsonKey(name: 'status_id') String statusId, 40 | @Default('') @JsonKey(name: 'design_id') String designId, 41 | @Default('') String number, 42 | @Default(0) double discount, 43 | @Default('') @JsonKey(name: 'po_number') String poNumber, 44 | @Default('') String date, 45 | @Default('') @JsonKey(name: 'last_sent_date') String lastSentDate, 46 | @Default('') @JsonKey(name: 'next_send_date') String nextSendDate, 47 | @Default('') @JsonKey(name: 'due_date') String validUntil, 48 | @Default('') String terms, 49 | @Default('') @JsonKey(name: 'public_notes') String publicNotes, 50 | @Default('') @JsonKey(name: 'private_notes') String privateNotes, 51 | @Default(false) 52 | @JsonKey(name: 'uses_inclusive_taxes') 53 | bool usesInclusiveTaxes, 54 | @Default('') @JsonKey(name: 'tax_name1') String taxName1, 55 | @Default(0) @JsonKey(name: 'tax_rate1') double taxRate1, 56 | @Default('') @JsonKey(name: 'tax_name2') String taxName2, 57 | @Default(0) @JsonKey(name: 'tax_rate2') double taxRate2, 58 | @Default('') @JsonKey(name: 'tax_name3') String taxName3, 59 | @Default(0) @JsonKey(name: 'tax_rate3') double taxRate3, 60 | @Default(0) @JsonKey(name: 'total_taxes') double totalTaxes, 61 | @Default(false) @JsonKey(name: 'is_amount_discount') bool isAmountDiscount, 62 | @Default('') String footer, 63 | @Default(0) double partial, 64 | @Default('') @JsonKey(name: 'partial_due_date') String partialDueDate, 65 | @Default(false) @JsonKey(name: 'has_tasks') bool hasTasks, 66 | @Default(false) @JsonKey(name: 'has_expenses') bool hasExpenses, 67 | @Default(0) @JsonKey(name: 'custom_surcharge1') double customSurcharge1, 68 | @Default(0) @JsonKey(name: 'custom_surcharge2') double customSurcharge2, 69 | @Default(0) @JsonKey(name: 'custom_surcharge3') double customSurcharge3, 70 | @Default(0) @JsonKey(name: 'custom_surcharge4') double customSurcharge4, 71 | @Default(false) 72 | @JsonKey(name: 'custom_surcharge_tax1') 73 | bool customSurchargeTax1, 74 | @Default(false) 75 | @JsonKey(name: 'custom_surcharge_tax2') 76 | bool customSurchargeTax2, 77 | @Default(false) 78 | @JsonKey(name: 'custom_surcharge_tax3') 79 | bool customSurchargeTax3, 80 | @Default(false) 81 | @JsonKey(name: 'custom_surcharge_tax4') 82 | bool customSurchargeTax4, 83 | @Default([]) List documents, 84 | @Default('') @JsonKey(name: 'vendor_id') String vendorId, 85 | }) = _Quote; 86 | 87 | /// Get the default invitation URL 88 | String get url => invitations.first.url; 89 | 90 | /// Create an invoice for a client 91 | factory Quote.forClient(Client client, {required List products}) { 92 | return Quote( 93 | clientId: client.id, 94 | lineItems: (products).map((product) => product.toLineItem).toList(), 95 | ); 96 | } 97 | 98 | /// Get the default invitation PDF URL 99 | String get pdfUrl => invitations.first.pdfUrl; 100 | 101 | /// Get the default invitation key 102 | String get key => invitations.first.key; 103 | 104 | /// Determine if the quote is paid 105 | bool get isPaid => statusId == '4'; 106 | 107 | /// Create an Quote from JSON 108 | factory Quote.fromJson(Map json) => _$QuoteFromJson(json); 109 | } 110 | 111 | /// Multi-item quote response 112 | @freezed 113 | class QuoteList with _$QuoteList { 114 | /// QuoteList factory constructor 115 | factory QuoteList({required List data}) = _QuoteList; 116 | 117 | /// Create an QuoteList from JSON 118 | factory QuoteList.fromJson(Map json) => 119 | _$QuoteListFromJson(json); 120 | } 121 | 122 | /// Single-item quote response 123 | @freezed 124 | class QuoteItem with _$QuoteItem { 125 | /// QuoteItem factory constructor 126 | factory QuoteItem(Quote data) = _QuoteItem; 127 | 128 | /// Create an QuoteItem from JSON 129 | factory QuoteItem.fromJson(Map json) => 130 | _$QuoteItemFromJson(json); 131 | } 132 | 133 | enum QuoteAction { 134 | sendEmail, 135 | markSent, 136 | convertToInvoice, 137 | approve, 138 | } 139 | -------------------------------------------------------------------------------- /lib/models/quote.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'quote.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_Quote _$$_QuoteFromJson(Map json) => _$_Quote( 10 | id: json['id'] as String? ?? '', 11 | createdById: json['user_id'] as String? ?? '', 12 | assignedToId: json['assigned_user_id'] as String? ?? '', 13 | createdAt: json['created_at'] as int? ?? 0, 14 | updatedAt: json['updated_at'] as int? ?? 0, 15 | archivedAt: json['archived_at'] as int? ?? 0, 16 | isDeleted: json['is_deleted'] as bool? ?? false, 17 | customValue1: json['custom_value1'] as String? ?? '', 18 | customValue2: json['custom_value2'] as String? ?? '', 19 | customValue3: json['custom_value3'] as String? ?? '', 20 | customValue4: json['custom_value4'] as String? ?? '', 21 | clientId: json['client_id'] as String? ?? '', 22 | lineItems: (json['line_items'] as List?) 23 | ?.map((e) => InvoiceLineItem.fromJson(e as Map)) 24 | .toList() ?? 25 | const [], 26 | invitations: (json['invitations'] as List?) 27 | ?.map( 28 | (e) => InvoiceInvitation.fromJson(e as Map)) 29 | .toList() ?? 30 | const [], 31 | amount: (json['amount'] as num?)?.toDouble() ?? 0, 32 | balance: (json['balance'] as num?)?.toDouble() ?? 0, 33 | statusId: json['status_id'] as String? ?? '', 34 | designId: json['design_id'] as String? ?? '', 35 | number: json['number'] as String? ?? '', 36 | discount: (json['discount'] as num?)?.toDouble() ?? 0, 37 | poNumber: json['po_number'] as String? ?? '', 38 | date: json['date'] as String? ?? '', 39 | lastSentDate: json['last_sent_date'] as String? ?? '', 40 | nextSendDate: json['next_send_date'] as String? ?? '', 41 | validUntil: json['due_date'] as String? ?? '', 42 | terms: json['terms'] as String? ?? '', 43 | publicNotes: json['public_notes'] as String? ?? '', 44 | privateNotes: json['private_notes'] as String? ?? '', 45 | usesInclusiveTaxes: json['uses_inclusive_taxes'] as bool? ?? false, 46 | taxName1: json['tax_name1'] as String? ?? '', 47 | taxRate1: (json['tax_rate1'] as num?)?.toDouble() ?? 0, 48 | taxName2: json['tax_name2'] as String? ?? '', 49 | taxRate2: (json['tax_rate2'] as num?)?.toDouble() ?? 0, 50 | taxName3: json['tax_name3'] as String? ?? '', 51 | taxRate3: (json['tax_rate3'] as num?)?.toDouble() ?? 0, 52 | totalTaxes: (json['total_taxes'] as num?)?.toDouble() ?? 0, 53 | isAmountDiscount: json['is_amount_discount'] as bool? ?? false, 54 | footer: json['footer'] as String? ?? '', 55 | partial: (json['partial'] as num?)?.toDouble() ?? 0, 56 | partialDueDate: json['partial_due_date'] as String? ?? '', 57 | hasTasks: json['has_tasks'] as bool? ?? false, 58 | hasExpenses: json['has_expenses'] as bool? ?? false, 59 | customSurcharge1: (json['custom_surcharge1'] as num?)?.toDouble() ?? 0, 60 | customSurcharge2: (json['custom_surcharge2'] as num?)?.toDouble() ?? 0, 61 | customSurcharge3: (json['custom_surcharge3'] as num?)?.toDouble() ?? 0, 62 | customSurcharge4: (json['custom_surcharge4'] as num?)?.toDouble() ?? 0, 63 | customSurchargeTax1: json['custom_surcharge_tax1'] as bool? ?? false, 64 | customSurchargeTax2: json['custom_surcharge_tax2'] as bool? ?? false, 65 | customSurchargeTax3: json['custom_surcharge_tax3'] as bool? ?? false, 66 | customSurchargeTax4: json['custom_surcharge_tax4'] as bool? ?? false, 67 | documents: (json['documents'] as List?) 68 | ?.map((e) => Document.fromJson(e as Map)) 69 | .toList() ?? 70 | const [], 71 | vendorId: json['vendor_id'] as String? ?? '', 72 | ); 73 | 74 | Map _$$_QuoteToJson(_$_Quote instance) => { 75 | 'id': instance.id, 76 | 'user_id': instance.createdById, 77 | 'assigned_user_id': instance.assignedToId, 78 | 'created_at': instance.createdAt, 79 | 'updated_at': instance.updatedAt, 80 | 'archived_at': instance.archivedAt, 81 | 'is_deleted': instance.isDeleted, 82 | 'custom_value1': instance.customValue1, 83 | 'custom_value2': instance.customValue2, 84 | 'custom_value3': instance.customValue3, 85 | 'custom_value4': instance.customValue4, 86 | 'client_id': instance.clientId, 87 | 'line_items': instance.lineItems.map((e) => e.toJson()).toList(), 88 | 'invitations': instance.invitations.map((e) => e.toJson()).toList(), 89 | 'amount': instance.amount, 90 | 'balance': instance.balance, 91 | 'status_id': instance.statusId, 92 | 'design_id': instance.designId, 93 | 'number': instance.number, 94 | 'discount': instance.discount, 95 | 'po_number': instance.poNumber, 96 | 'date': instance.date, 97 | 'last_sent_date': instance.lastSentDate, 98 | 'next_send_date': instance.nextSendDate, 99 | 'due_date': instance.validUntil, 100 | 'terms': instance.terms, 101 | 'public_notes': instance.publicNotes, 102 | 'private_notes': instance.privateNotes, 103 | 'uses_inclusive_taxes': instance.usesInclusiveTaxes, 104 | 'tax_name1': instance.taxName1, 105 | 'tax_rate1': instance.taxRate1, 106 | 'tax_name2': instance.taxName2, 107 | 'tax_rate2': instance.taxRate2, 108 | 'tax_name3': instance.taxName3, 109 | 'tax_rate3': instance.taxRate3, 110 | 'total_taxes': instance.totalTaxes, 111 | 'is_amount_discount': instance.isAmountDiscount, 112 | 'footer': instance.footer, 113 | 'partial': instance.partial, 114 | 'partial_due_date': instance.partialDueDate, 115 | 'has_tasks': instance.hasTasks, 116 | 'has_expenses': instance.hasExpenses, 117 | 'custom_surcharge1': instance.customSurcharge1, 118 | 'custom_surcharge2': instance.customSurcharge2, 119 | 'custom_surcharge3': instance.customSurcharge3, 120 | 'custom_surcharge4': instance.customSurcharge4, 121 | 'custom_surcharge_tax1': instance.customSurchargeTax1, 122 | 'custom_surcharge_tax2': instance.customSurchargeTax2, 123 | 'custom_surcharge_tax3': instance.customSurchargeTax3, 124 | 'custom_surcharge_tax4': instance.customSurchargeTax4, 125 | 'documents': instance.documents.map((e) => e.toJson()).toList(), 126 | 'vendor_id': instance.vendorId, 127 | }; 128 | 129 | _$_QuoteList _$$_QuoteListFromJson(Map json) => _$_QuoteList( 130 | data: (json['data'] as List) 131 | .map((e) => Quote.fromJson(e as Map)) 132 | .toList(), 133 | ); 134 | 135 | Map _$$_QuoteListToJson(_$_QuoteList instance) => 136 | { 137 | 'data': instance.data, 138 | }; 139 | 140 | _$_QuoteItem _$$_QuoteItemFromJson(Map json) => _$_QuoteItem( 141 | Quote.fromJson(json['data'] as Map), 142 | ); 143 | 144 | Map _$$_QuoteItemToJson(_$_QuoteItem instance) => 145 | { 146 | 'data': instance.data, 147 | }; 148 | -------------------------------------------------------------------------------- /lib/models/recurring_invoice.dart: -------------------------------------------------------------------------------- 1 | import 'package:freezed_annotation/freezed_annotation.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:invoiceninja/models/client.dart'; 4 | import 'package:invoiceninja/models/document.dart'; 5 | import 'package:invoiceninja/models/invoice.dart'; 6 | import 'package:invoiceninja/models/product.dart'; 7 | 8 | part 'recurring_invoice.freezed.dart'; 9 | 10 | part 'recurring_invoice.g.dart'; 11 | 12 | /// RecurringInvoice class 13 | @freezed 14 | class RecurringInvoice with _$RecurringInvoice { 15 | /// Default constructor 16 | const RecurringInvoice._(); 17 | 18 | /// RecurringInvoice factory constructor 19 | @JsonSerializable(explicitToJson: true) 20 | const factory RecurringInvoice({ 21 | @Default('') String id, 22 | @Default('') @JsonKey(name: 'user_id') String createdById, 23 | @Default('') @JsonKey(name: 'assigned_user_id') String assignedToId, 24 | @Default(0) @JsonKey(name: 'created_at') int createdAt, 25 | @Default(0) @JsonKey(name: 'updated_at') int updatedAt, 26 | @Default(0) @JsonKey(name: 'archived_at') int archivedAt, 27 | @Default(false) @JsonKey(name: 'is_deleted') bool isDeleted, 28 | @Default('') @JsonKey(name: 'custom_value1') String customValue1, 29 | @Default('') @JsonKey(name: 'custom_value2') String customValue2, 30 | @Default('') @JsonKey(name: 'custom_value3') String customValue3, 31 | @Default('') @JsonKey(name: 'custom_value4') String customValue4, 32 | @Default('') @JsonKey(name: 'client_id') String clientId, 33 | @Default([]) 34 | @JsonKey(name: 'line_items') 35 | List lineItems, 36 | @Default([]) List invitations, 37 | @Default(0) double amount, 38 | @Default(0) double balance, 39 | @Default('') @JsonKey(name: 'status_id') String statusId, 40 | @Default('') @JsonKey(name: 'design_id') String designId, 41 | @Default('') String number, 42 | @Default(0) double discount, 43 | @Default('') @JsonKey(name: 'po_number') String poNumber, 44 | @Default('') String date, 45 | @Default('') @JsonKey(name: 'last_sent_date') String lastSentDate, 46 | @Default('') @JsonKey(name: 'next_send_date') String nextSendDate, 47 | @Default('') @JsonKey(name: 'due_date') String dueDate, 48 | @Default('') String terms, 49 | @Default('') @JsonKey(name: 'public_notes') String publicNotes, 50 | @Default('') @JsonKey(name: 'private_notes') String privateNotes, 51 | @Default(false) 52 | @JsonKey(name: 'uses_inclusive_taxes') 53 | bool usesInclusiveTaxes, 54 | @Default('') @JsonKey(name: 'tax_name1') String taxName1, 55 | @Default(0) @JsonKey(name: 'tax_rate1') double taxRate1, 56 | @Default('') @JsonKey(name: 'tax_name2') String taxName2, 57 | @Default(0) @JsonKey(name: 'tax_rate2') double taxRate2, 58 | @Default('') @JsonKey(name: 'tax_name3') String taxName3, 59 | @Default(0) @JsonKey(name: 'tax_rate3') double taxRate3, 60 | @Default(0) @JsonKey(name: 'total_taxes') double totalTaxes, 61 | @Default(false) @JsonKey(name: 'is_amount_discount') bool isAmountDiscount, 62 | @Default('') String footer, 63 | @Default(0) double partial, 64 | @Default('') @JsonKey(name: 'partial_due_date') String partialDueDate, 65 | @Default(false) @JsonKey(name: 'has_tasks') bool hasTasks, 66 | @Default(false) @JsonKey(name: 'has_expenses') bool hasExpenses, 67 | @Default(0) @JsonKey(name: 'custom_surcharge1') double customSurcharge1, 68 | @Default(0) @JsonKey(name: 'custom_surcharge2') double customSurcharge2, 69 | @Default(0) @JsonKey(name: 'custom_surcharge3') double customSurcharge3, 70 | @Default(0) @JsonKey(name: 'custom_surcharge4') double customSurcharge4, 71 | @Default(false) 72 | @JsonKey(name: 'custom_surcharge_tax1') 73 | bool customSurchargeTax1, 74 | @Default(false) 75 | @JsonKey(name: 'custom_surcharge_tax2') 76 | bool customSurchargeTax2, 77 | @Default(false) 78 | @JsonKey(name: 'custom_surcharge_tax3') 79 | bool customSurchargeTax3, 80 | @Default(false) 81 | @JsonKey(name: 'custom_surcharge_tax4') 82 | bool customSurchargeTax4, 83 | @Default([]) List documents, 84 | @Default('') @JsonKey(name: 'vendor_id') String vendorId, 85 | @Default('') @JsonKey(name: 'frequency_id') String frequencyId, 86 | @Default(0) @JsonKey(name: 'remaining_cycles') int remainingCycles, 87 | @Default('') @JsonKey(name: 'due_date_days') String dueDateDays, 88 | }) = _RecurringInvoice; 89 | 90 | /// Create an invoice for a client 91 | factory RecurringInvoice.forClient(Client client, 92 | {required List products}) { 93 | return RecurringInvoice( 94 | clientId: client.id, 95 | lineItems: (products).map((product) => product.toLineItem).toList(), 96 | ); 97 | } 98 | 99 | /// Get the default invitation URL 100 | String get url => invitations.first.url; 101 | 102 | /// Get the default invitation PDF URL 103 | String get pdfUrl => invitations.first.pdfUrl; 104 | 105 | /// Get the default invitation key 106 | String get key => invitations.first.key; 107 | 108 | /// Determine if the invoice is paid 109 | bool get isPaid => statusId == '4'; 110 | 111 | /// Create an RecurringInvoice from JSON 112 | factory RecurringInvoice.fromJson(Map json) => 113 | _$RecurringInvoiceFromJson(json); 114 | } 115 | 116 | /// Multi-item invoice response 117 | @freezed 118 | class RecurringInvoiceList with _$RecurringInvoiceList { 119 | /// RecurringInvoiceList factory constructor 120 | factory RecurringInvoiceList({required List data}) = 121 | _RecurringInvoiceList; 122 | 123 | /// Create an RecurringInvoiceList from JSON 124 | factory RecurringInvoiceList.fromJson(Map json) => 125 | _$RecurringInvoiceListFromJson(json); 126 | } 127 | 128 | /// Single-item invoice response 129 | @freezed 130 | class RecurringInvoiceItem with _$RecurringInvoiceItem { 131 | /// RecurringInvoiceItem factory constructor 132 | factory RecurringInvoiceItem(RecurringInvoice data) = _RecurringInvoiceItem; 133 | 134 | /// Create an RecurringInvoiceItem from JSON 135 | factory RecurringInvoiceItem.fromJson(Map json) => 136 | _$RecurringInvoiceItemFromJson(json); 137 | } 138 | 139 | enum RecurringInvoiceAction { 140 | start, 141 | stop, 142 | sendNow, 143 | } 144 | -------------------------------------------------------------------------------- /lib/models/recurring_invoice.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'recurring_invoice.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | _$_RecurringInvoice _$$_RecurringInvoiceFromJson(Map json) => 10 | _$_RecurringInvoice( 11 | id: json['id'] as String? ?? '', 12 | createdById: json['user_id'] as String? ?? '', 13 | assignedToId: json['assigned_user_id'] as String? ?? '', 14 | createdAt: json['created_at'] as int? ?? 0, 15 | updatedAt: json['updated_at'] as int? ?? 0, 16 | archivedAt: json['archived_at'] as int? ?? 0, 17 | isDeleted: json['is_deleted'] as bool? ?? false, 18 | customValue1: json['custom_value1'] as String? ?? '', 19 | customValue2: json['custom_value2'] as String? ?? '', 20 | customValue3: json['custom_value3'] as String? ?? '', 21 | customValue4: json['custom_value4'] as String? ?? '', 22 | clientId: json['client_id'] as String? ?? '', 23 | lineItems: (json['line_items'] as List?) 24 | ?.map((e) => InvoiceLineItem.fromJson(e as Map)) 25 | .toList() ?? 26 | const [], 27 | invitations: (json['invitations'] as List?) 28 | ?.map( 29 | (e) => InvoiceInvitation.fromJson(e as Map)) 30 | .toList() ?? 31 | const [], 32 | amount: (json['amount'] as num?)?.toDouble() ?? 0, 33 | balance: (json['balance'] as num?)?.toDouble() ?? 0, 34 | statusId: json['status_id'] as String? ?? '', 35 | designId: json['design_id'] as String? ?? '', 36 | number: json['number'] as String? ?? '', 37 | discount: (json['discount'] as num?)?.toDouble() ?? 0, 38 | poNumber: json['po_number'] as String? ?? '', 39 | date: json['date'] as String? ?? '', 40 | lastSentDate: json['last_sent_date'] as String? ?? '', 41 | nextSendDate: json['next_send_date'] as String? ?? '', 42 | dueDate: json['due_date'] as String? ?? '', 43 | terms: json['terms'] as String? ?? '', 44 | publicNotes: json['public_notes'] as String? ?? '', 45 | privateNotes: json['private_notes'] as String? ?? '', 46 | usesInclusiveTaxes: json['uses_inclusive_taxes'] as bool? ?? false, 47 | taxName1: json['tax_name1'] as String? ?? '', 48 | taxRate1: (json['tax_rate1'] as num?)?.toDouble() ?? 0, 49 | taxName2: json['tax_name2'] as String? ?? '', 50 | taxRate2: (json['tax_rate2'] as num?)?.toDouble() ?? 0, 51 | taxName3: json['tax_name3'] as String? ?? '', 52 | taxRate3: (json['tax_rate3'] as num?)?.toDouble() ?? 0, 53 | totalTaxes: (json['total_taxes'] as num?)?.toDouble() ?? 0, 54 | isAmountDiscount: json['is_amount_discount'] as bool? ?? false, 55 | footer: json['footer'] as String? ?? '', 56 | partial: (json['partial'] as num?)?.toDouble() ?? 0, 57 | partialDueDate: json['partial_due_date'] as String? ?? '', 58 | hasTasks: json['has_tasks'] as bool? ?? false, 59 | hasExpenses: json['has_expenses'] as bool? ?? false, 60 | customSurcharge1: (json['custom_surcharge1'] as num?)?.toDouble() ?? 0, 61 | customSurcharge2: (json['custom_surcharge2'] as num?)?.toDouble() ?? 0, 62 | customSurcharge3: (json['custom_surcharge3'] as num?)?.toDouble() ?? 0, 63 | customSurcharge4: (json['custom_surcharge4'] as num?)?.toDouble() ?? 0, 64 | customSurchargeTax1: json['custom_surcharge_tax1'] as bool? ?? false, 65 | customSurchargeTax2: json['custom_surcharge_tax2'] as bool? ?? false, 66 | customSurchargeTax3: json['custom_surcharge_tax3'] as bool? ?? false, 67 | customSurchargeTax4: json['custom_surcharge_tax4'] as bool? ?? false, 68 | documents: (json['documents'] as List?) 69 | ?.map((e) => Document.fromJson(e as Map)) 70 | .toList() ?? 71 | const [], 72 | vendorId: json['vendor_id'] as String? ?? '', 73 | frequencyId: json['frequency_id'] as String? ?? '', 74 | remainingCycles: json['remaining_cycles'] as int? ?? 0, 75 | dueDateDays: json['due_date_days'] as String? ?? '', 76 | ); 77 | 78 | Map _$$_RecurringInvoiceToJson(_$_RecurringInvoice instance) => 79 | { 80 | 'id': instance.id, 81 | 'user_id': instance.createdById, 82 | 'assigned_user_id': instance.assignedToId, 83 | 'created_at': instance.createdAt, 84 | 'updated_at': instance.updatedAt, 85 | 'archived_at': instance.archivedAt, 86 | 'is_deleted': instance.isDeleted, 87 | 'custom_value1': instance.customValue1, 88 | 'custom_value2': instance.customValue2, 89 | 'custom_value3': instance.customValue3, 90 | 'custom_value4': instance.customValue4, 91 | 'client_id': instance.clientId, 92 | 'line_items': instance.lineItems.map((e) => e.toJson()).toList(), 93 | 'invitations': instance.invitations.map((e) => e.toJson()).toList(), 94 | 'amount': instance.amount, 95 | 'balance': instance.balance, 96 | 'status_id': instance.statusId, 97 | 'design_id': instance.designId, 98 | 'number': instance.number, 99 | 'discount': instance.discount, 100 | 'po_number': instance.poNumber, 101 | 'date': instance.date, 102 | 'last_sent_date': instance.lastSentDate, 103 | 'next_send_date': instance.nextSendDate, 104 | 'due_date': instance.dueDate, 105 | 'terms': instance.terms, 106 | 'public_notes': instance.publicNotes, 107 | 'private_notes': instance.privateNotes, 108 | 'uses_inclusive_taxes': instance.usesInclusiveTaxes, 109 | 'tax_name1': instance.taxName1, 110 | 'tax_rate1': instance.taxRate1, 111 | 'tax_name2': instance.taxName2, 112 | 'tax_rate2': instance.taxRate2, 113 | 'tax_name3': instance.taxName3, 114 | 'tax_rate3': instance.taxRate3, 115 | 'total_taxes': instance.totalTaxes, 116 | 'is_amount_discount': instance.isAmountDiscount, 117 | 'footer': instance.footer, 118 | 'partial': instance.partial, 119 | 'partial_due_date': instance.partialDueDate, 120 | 'has_tasks': instance.hasTasks, 121 | 'has_expenses': instance.hasExpenses, 122 | 'custom_surcharge1': instance.customSurcharge1, 123 | 'custom_surcharge2': instance.customSurcharge2, 124 | 'custom_surcharge3': instance.customSurcharge3, 125 | 'custom_surcharge4': instance.customSurcharge4, 126 | 'custom_surcharge_tax1': instance.customSurchargeTax1, 127 | 'custom_surcharge_tax2': instance.customSurchargeTax2, 128 | 'custom_surcharge_tax3': instance.customSurchargeTax3, 129 | 'custom_surcharge_tax4': instance.customSurchargeTax4, 130 | 'documents': instance.documents.map((e) => e.toJson()).toList(), 131 | 'vendor_id': instance.vendorId, 132 | 'frequency_id': instance.frequencyId, 133 | 'remaining_cycles': instance.remainingCycles, 134 | 'due_date_days': instance.dueDateDays, 135 | }; 136 | 137 | _$_RecurringInvoiceList _$$_RecurringInvoiceListFromJson( 138 | Map json) => 139 | _$_RecurringInvoiceList( 140 | data: (json['data'] as List) 141 | .map((e) => RecurringInvoice.fromJson(e as Map)) 142 | .toList(), 143 | ); 144 | 145 | Map _$$_RecurringInvoiceListToJson( 146 | _$_RecurringInvoiceList instance) => 147 | { 148 | 'data': instance.data, 149 | }; 150 | 151 | _$_RecurringInvoiceItem _$$_RecurringInvoiceItemFromJson( 152 | Map json) => 153 | _$_RecurringInvoiceItem( 154 | RecurringInvoice.fromJson(json['data'] as Map), 155 | ); 156 | 157 | Map _$$_RecurringInvoiceItemToJson( 158 | _$_RecurringInvoiceItem instance) => 159 | { 160 | 'data': instance.data, 161 | }; 162 | -------------------------------------------------------------------------------- /lib/repositories/client_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:invoiceninja/invoiceninja.dart'; 2 | import 'package:invoiceninja/models/client.dart'; 3 | import 'package:invoiceninja/utils/web_client.dart'; 4 | 5 | /// Client Repository 6 | class ClientRepository { 7 | /// Persist client to the server 8 | Future save(Client client) async { 9 | dynamic response = await WebClient().post( 10 | '${InvoiceNinja.url}/api/v1/shop/clients', 11 | companyKey: InvoiceNinja.companyKey, 12 | data: client.toJson()); 13 | 14 | return ClientItem.fromJson(response).data; 15 | } 16 | 17 | /// Find a client by its contact key 18 | Future findByKey(String key) async { 19 | final response = await WebClient().get( 20 | '${InvoiceNinja.url}/api/v1/shop/client/$key', 21 | companyKey: InvoiceNinja.companyKey); 22 | 23 | return ClientItem.fromJson(response).data; 24 | } 25 | } 26 | 27 | /// Client Admin Repository 28 | class ClientAdminRepository { 29 | /// Load list of clients 30 | Future> load() async { 31 | final response = await WebClient().get( 32 | '${InvoiceNinjaAdmin.url}/api/v1/clients', 33 | token: InvoiceNinjaAdmin.token); 34 | 35 | return ClientList.fromJson(response).data; 36 | } 37 | 38 | /// Persist client to the server 39 | Future save(Client client) async { 40 | dynamic response; 41 | 42 | if (client.id.isEmpty) { 43 | response = await WebClient().post( 44 | '${InvoiceNinjaAdmin.url}/api/v1/clients', 45 | token: InvoiceNinjaAdmin.token, 46 | data: client.toJson()); 47 | } else { 48 | response = await WebClient().put( 49 | '${InvoiceNinjaAdmin.url}/api/v1/clients/${client.id}', 50 | token: InvoiceNinjaAdmin.token, 51 | data: client.toJson()); 52 | } 53 | 54 | return ClientItem.fromJson(response).data; 55 | } 56 | 57 | /// Find client by its id 58 | Future findById(String id) async { 59 | final response = await WebClient().get( 60 | '${InvoiceNinjaAdmin.url}/api/v1/clients/$id', 61 | token: InvoiceNinjaAdmin.token); 62 | 63 | return ClientItem.fromJson(response).data; 64 | } 65 | 66 | /// Find client by its contact email address 67 | Future findByEmail(String email) async { 68 | final response = await WebClient().get( 69 | '${InvoiceNinjaAdmin.url}/api/v1/clients?email=$email', 70 | token: InvoiceNinjaAdmin.token); 71 | 72 | final data = ClientList.fromJson(response).data; 73 | 74 | return data.isEmpty ? null : data.first; 75 | } 76 | 77 | /// Find client by its id number 78 | Future findByIdNumber(String idNumber) async { 79 | final response = await WebClient().get( 80 | '${InvoiceNinjaAdmin.url}/api/v1/clients?id_number=$idNumber', 81 | token: InvoiceNinjaAdmin.token); 82 | 83 | final data = ClientList.fromJson(response).data; 84 | 85 | return data.isEmpty ? null : data.first; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/repositories/company_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:invoiceninja/invoiceninja.dart'; 2 | import 'package:invoiceninja/models/company.dart'; 3 | import 'package:invoiceninja/utils/web_client.dart'; 4 | 5 | /// Profile Repository 6 | class CompanyRepository { 7 | /// Load profile 8 | Future load() async { 9 | final response = await WebClient().get( 10 | '${InvoiceNinja.url}/api/v1/shop/profile', 11 | companyKey: InvoiceNinja.companyKey); 12 | 13 | return CompanyItem.fromJson(response).data; 14 | } 15 | } -------------------------------------------------------------------------------- /lib/repositories/credit_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:invoiceninja/models/credit.dart'; 2 | import 'package:invoiceninja/utils/web_client.dart'; 3 | 4 | import '../invoiceninja.dart'; 5 | 6 | /// Credit Admin Repository 7 | class CreditAdminRepository { 8 | /// Load list of credits 9 | Future> load() async { 10 | final response = await WebClient().get( 11 | '${InvoiceNinjaAdmin.url}/api/v1/credits', 12 | token: InvoiceNinjaAdmin.token); 13 | 14 | return CreditList.fromJson(response).data; 15 | } 16 | 17 | /// Persist credit to the server 18 | Future save(Credit credit, {CreditAction? action}) async { 19 | dynamic response; 20 | String url; 21 | 22 | if (credit.id.isEmpty) { 23 | url = '${InvoiceNinjaAdmin.url}/api/v1/credits'; 24 | } else { 25 | url = '${InvoiceNinjaAdmin.url}/api/v1/credits/${credit.id}'; 26 | } 27 | 28 | if (action == CreditAction.markSent) { 29 | url += '?mark_sent=true'; 30 | } else if (action == CreditAction.sendEmail) { 31 | url += '?send_email=true'; 32 | } 33 | 34 | if (credit.id.isEmpty) { 35 | response = await WebClient() 36 | .post(url, token: InvoiceNinjaAdmin.token, data: credit.toJson()); 37 | } else { 38 | response = await WebClient() 39 | .put(url, token: InvoiceNinjaAdmin.token, data: credit.toJson()); 40 | } 41 | 42 | return CreditItem.fromJson(response).data; 43 | } 44 | 45 | /// Find an credit by its id 46 | Future findById(String id) async { 47 | final response = await WebClient().get( 48 | '${InvoiceNinjaAdmin.url}/api/v1/credits/$id', 49 | token: InvoiceNinjaAdmin.token); 50 | 51 | return CreditItem.fromJson(response).data; 52 | } 53 | 54 | /// Find an credit by its number 55 | Future findByNumber(String number) async { 56 | final response = await WebClient().get( 57 | '${InvoiceNinjaAdmin.url}/api/v1/credits?number=$number', 58 | token: InvoiceNinjaAdmin.token); 59 | 60 | final data = CreditList.fromJson(response).data; 61 | 62 | return data.isEmpty ? null : data.first; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/repositories/invoice_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:invoiceninja/invoiceninja.dart'; 2 | import 'package:invoiceninja/models/invoice.dart'; 3 | import 'package:invoiceninja/utils/web_client.dart'; 4 | 5 | /// Invoice Repository 6 | class InvoiceRepository { 7 | /// Persist invoice to the server 8 | Future save(Invoice invoice) async { 9 | dynamic response = await WebClient().post( 10 | '${InvoiceNinja.url}/api/v1/shop/invoices', 11 | companyKey: InvoiceNinja.companyKey, 12 | data: invoice.toJson()); 13 | 14 | return InvoiceItem.fromJson(response).data; 15 | } 16 | 17 | /// Find an invoice by its invitation key 18 | Future findByKey(String key) async { 19 | final response = await WebClient().get( 20 | '${InvoiceNinja.url}/api/v1/shop/invoice/$key', 21 | companyKey: InvoiceNinja.companyKey); 22 | 23 | return InvoiceItem.fromJson(response).data; 24 | } 25 | } 26 | 27 | /// Invoice Admin Repository 28 | class InvoiceAdminRepository { 29 | /// Load list of invoices 30 | Future> load() async { 31 | final response = await WebClient().get( 32 | '${InvoiceNinjaAdmin.url}/api/v1/invoices', 33 | token: InvoiceNinjaAdmin.token); 34 | 35 | return InvoiceList.fromJson(response).data; 36 | } 37 | 38 | /// Persist invoice to the server 39 | Future save( 40 | Invoice invoice, { 41 | InvoiceAction? action, 42 | }) async { 43 | dynamic response; 44 | String url; 45 | 46 | if (invoice.id.isEmpty) { 47 | url = '${InvoiceNinjaAdmin.url}/api/v1/invoices'; 48 | } else { 49 | url = '${InvoiceNinjaAdmin.url}/api/v1/invoices/${invoice.id}'; 50 | } 51 | 52 | if (action == InvoiceAction.markPaid) { 53 | url += '?paid=true'; 54 | } else if (action == InvoiceAction.markSent) { 55 | url += '?mark_sent=true'; 56 | } else if (action == InvoiceAction.sendEmail) { 57 | url += '?send_email=true'; 58 | } else if (action == InvoiceAction.autoBill) { 59 | url += '?auto_bill=true'; 60 | } 61 | 62 | if (invoice.id.isEmpty) { 63 | response = await WebClient() 64 | .post(url, token: InvoiceNinjaAdmin.token, data: invoice.toJson()); 65 | } else { 66 | response = await WebClient() 67 | .put(url, token: InvoiceNinjaAdmin.token, data: invoice.toJson()); 68 | } 69 | 70 | return InvoiceItem.fromJson(response).data; 71 | } 72 | 73 | /// Find an invoice by its id 74 | Future findById(String id) async { 75 | final response = await WebClient().get( 76 | '${InvoiceNinjaAdmin.url}/api/v1/invoices/$id', 77 | token: InvoiceNinjaAdmin.token); 78 | 79 | return InvoiceItem.fromJson(response).data; 80 | } 81 | 82 | /// Find an invoice by its number 83 | Future findByNumber(String number) async { 84 | final response = await WebClient().get( 85 | '${InvoiceNinjaAdmin.url}/api/v1/invoices?invoice_number=$number', 86 | token: InvoiceNinjaAdmin.token); 87 | 88 | final data = InvoiceList.fromJson(response).data; 89 | 90 | return data.isEmpty ? null : data.first; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/repositories/payment_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:invoiceninja/models/payment.dart'; 2 | 3 | import '../invoiceninja.dart'; 4 | import '../utils/web_client.dart'; 5 | 6 | class PaymentAdminRepository { 7 | /// Load list of payments 8 | Future> load() async { 9 | final response = await WebClient().get( 10 | '${InvoiceNinjaAdmin.url}/api/v1/payments', 11 | token: InvoiceNinjaAdmin.token); 12 | 13 | return PaymentList.fromJson(response).data; 14 | } 15 | 16 | /// Persist payment to the server 17 | Future save(Payment payment, {PaymentAction? action}) async { 18 | dynamic response; 19 | String url; 20 | 21 | if (payment.id.isEmpty) { 22 | url = '${InvoiceNinjaAdmin.url}/api/v1/payments'; 23 | } else { 24 | url = '${InvoiceNinjaAdmin.url}/api/v1/payments/${payment.id}'; 25 | } 26 | 27 | if (action == PaymentAction.sendEmail) { 28 | url += '?email_receipt=true'; 29 | } 30 | 31 | if (payment.id.isEmpty) { 32 | response = await WebClient() 33 | .post(url, token: InvoiceNinjaAdmin.token, data: payment.toJson()); 34 | } else { 35 | response = await WebClient() 36 | .put(url, token: InvoiceNinjaAdmin.token, data: payment.toJson()); 37 | } 38 | 39 | return PaymentItem.fromJson(response).data; 40 | } 41 | 42 | /// Find a payment by its id 43 | Future findById(String id) async { 44 | final response = await WebClient().get( 45 | '${InvoiceNinjaAdmin.url}/api/v1/payments/$id', 46 | token: InvoiceNinjaAdmin.token); 47 | 48 | return PaymentItem.fromJson(response).data; 49 | } 50 | 51 | /// Find a payment by its transaction reference 52 | Future findByTransactionReference( 53 | String transactionReference) async { 54 | final response = await WebClient().get( 55 | '${InvoiceNinjaAdmin.url}/api/v1/payments?transaction_reference=$transactionReference', 56 | token: InvoiceNinjaAdmin.token); 57 | 58 | final data = PaymentList.fromJson(response).data; 59 | 60 | return data.isEmpty ? null : data.first; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/repositories/product_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:invoiceninja/invoiceninja.dart'; 2 | import 'package:invoiceninja/models/product.dart'; 3 | import 'package:invoiceninja/utils/web_client.dart'; 4 | 5 | /// Product Repository 6 | class ProductRepository { 7 | /// Load list of products 8 | Future> load() async { 9 | final response = await WebClient().get( 10 | '${InvoiceNinja.url}/api/v1/shop/products', 11 | companyKey: InvoiceNinja.companyKey); 12 | 13 | return ProductList.fromJson(response).data; 14 | } 15 | 16 | /// Find a product by its key 17 | Future findByKey(String key) async { 18 | final response = await WebClient().get( 19 | '${InvoiceNinja.url}/api/v1/shop/product/$key', 20 | companyKey: InvoiceNinja.companyKey); 21 | 22 | return ProductItem.fromJson(response).data; 23 | } 24 | } 25 | 26 | /// Product Admin Repository 27 | class ProductAdminRepository { 28 | /// Load list of products 29 | Future> load() async { 30 | final response = await WebClient().get( 31 | '${InvoiceNinjaAdmin.url}/api/v1/products', 32 | token: InvoiceNinjaAdmin.token); 33 | 34 | return ProductList.fromJson(response).data; 35 | } 36 | 37 | /// Persist product to the server 38 | Future save(Product product) async { 39 | dynamic response; 40 | 41 | if (product.id.isEmpty) { 42 | response = await WebClient().post( 43 | '${InvoiceNinjaAdmin.url}/api/v1/products', 44 | token: InvoiceNinjaAdmin.token, 45 | data: product.toJson()); 46 | } else { 47 | response = await WebClient().put( 48 | '${InvoiceNinjaAdmin.url}/api/v1/products/${product.id}', 49 | token: InvoiceNinjaAdmin.token, 50 | data: product.toJson()); 51 | } 52 | 53 | return ProductItem.fromJson(response).data; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/repositories/quote_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:invoiceninja/models/quote.dart'; 2 | import 'package:invoiceninja/utils/web_client.dart'; 3 | 4 | import '../invoiceninja.dart'; 5 | 6 | /// Quote Admin Repository 7 | class QuoteAdminRepository { 8 | /// Load list of quotes 9 | Future> load() async { 10 | final response = await WebClient().get( 11 | '${InvoiceNinjaAdmin.url}/api/v1/quotes', 12 | token: InvoiceNinjaAdmin.token); 13 | 14 | return QuoteList.fromJson(response).data; 15 | } 16 | 17 | /// Persist quote to the server 18 | Future save(Quote quote, {QuoteAction? action}) async { 19 | dynamic response; 20 | String url; 21 | 22 | if (quote.id.isEmpty) { 23 | url = '${InvoiceNinjaAdmin.url}/api/v1/quotes'; 24 | } else { 25 | url = '${InvoiceNinjaAdmin.url}/api/v1/quotes/${quote.id}'; 26 | } 27 | 28 | if (action == QuoteAction.approve) { 29 | url += '?approve=true'; 30 | } else if (action == QuoteAction.markSent) { 31 | url += '?mark_sent=true'; 32 | } else if (action == QuoteAction.convertToInvoice) { 33 | url += '?convert=true'; 34 | } else if (action == QuoteAction.sendEmail) { 35 | url += '?send_email=true'; 36 | } 37 | 38 | if (quote.id.isEmpty) { 39 | response = await WebClient() 40 | .post(url, token: InvoiceNinjaAdmin.token, data: quote.toJson()); 41 | } else { 42 | response = await WebClient() 43 | .put(url, token: InvoiceNinjaAdmin.token, data: quote.toJson()); 44 | } 45 | 46 | return QuoteItem.fromJson(response).data; 47 | } 48 | 49 | /// Find an quote by its id 50 | Future findById(String id) async { 51 | final response = await WebClient().get( 52 | '${InvoiceNinjaAdmin.url}/api/v1/quotes/$id', 53 | token: InvoiceNinjaAdmin.token); 54 | 55 | return QuoteItem.fromJson(response).data; 56 | } 57 | 58 | /// Find an quote by its number 59 | Future findByNumber(String number) async { 60 | final response = await WebClient().get( 61 | '${InvoiceNinjaAdmin.url}/api/v1/quotes?number=$number', 62 | token: InvoiceNinjaAdmin.token); 63 | 64 | final data = QuoteList.fromJson(response).data; 65 | 66 | return data.isEmpty ? null : data.first; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/repositories/recurring_invoice_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:invoiceninja/invoiceninja.dart'; 2 | import 'package:invoiceninja/models/recurring_invoice.dart'; 3 | import 'package:invoiceninja/utils/web_client.dart'; 4 | 5 | /* 6 | /// RecurringInvoice Repository 7 | class RecurringInvoiceRepository { 8 | /// Persist invoice to the server 9 | Future save(RecurringInvoice invoice) async { 10 | dynamic response = await WebClient().post( 11 | '${InvoiceNinja.url}/api/v1/shop/invoices', 12 | companyKey: InvoiceNinja.companyKey, 13 | data: invoice.toJson()); 14 | 15 | return RecurringInvoiceItem.fromJson(response).data; 16 | } 17 | 18 | /// Find an invoice by its invitation key 19 | Future findByKey(String key) async { 20 | final response = await WebClient().get( 21 | '${InvoiceNinja.url}/api/v1/shop/invoice/$key', 22 | companyKey: InvoiceNinja.companyKey); 23 | 24 | return RecurringInvoiceItem.fromJson(response).data; 25 | } 26 | } 27 | */ 28 | 29 | /// RecurringInvoice Admin Repository 30 | class RecurringInvoiceAdminRepository { 31 | /// Load list of invoices 32 | Future> load() async { 33 | final response = await WebClient().get( 34 | '${InvoiceNinjaAdmin.url}/api/v1/recurring_invoices', 35 | token: InvoiceNinjaAdmin.token); 36 | 37 | return RecurringInvoiceList.fromJson(response).data; 38 | } 39 | 40 | /// Persist invoice to the server 41 | Future save( 42 | RecurringInvoice invoice, { 43 | RecurringInvoiceAction? action, 44 | }) async { 45 | dynamic response; 46 | String url; 47 | 48 | if (invoice.id.isEmpty) { 49 | url = '${InvoiceNinjaAdmin.url}/api/v1/recurring_invoices'; 50 | } else { 51 | url = '${InvoiceNinjaAdmin.url}/api/v1/recurring_invoices/${invoice.id}'; 52 | } 53 | 54 | if (action == RecurringInvoiceAction.sendNow) { 55 | url += '?send_now=true'; 56 | } else if (action == RecurringInvoiceAction.start) { 57 | url += '?start=true'; 58 | } else if (action == RecurringInvoiceAction.stop) { 59 | url += '?stop=true'; 60 | } 61 | 62 | if (invoice.id.isEmpty) { 63 | response = await WebClient() 64 | .post(url, token: InvoiceNinjaAdmin.token, data: invoice.toJson()); 65 | } else { 66 | response = await WebClient() 67 | .put(url, token: InvoiceNinjaAdmin.token, data: invoice.toJson()); 68 | } 69 | 70 | return RecurringInvoiceItem.fromJson(response).data; 71 | } 72 | 73 | /// Find an invoice by its id 74 | Future findById(String id) async { 75 | final response = await WebClient().get( 76 | '${InvoiceNinjaAdmin.url}/api/v1/recurring_invoices/$id', 77 | token: InvoiceNinjaAdmin.token); 78 | 79 | return RecurringInvoiceItem.fromJson(response).data; 80 | } 81 | 82 | /// Find an invoice by its number 83 | Future findByNumber(String number) async { 84 | final response = await WebClient().get( 85 | '${InvoiceNinjaAdmin.url}/api/v1/recurring_invoices?invoice_number=$number', 86 | token: InvoiceNinjaAdmin.token); 87 | 88 | final data = RecurringInvoiceList.fromJson(response).data; 89 | 90 | return data.isEmpty ? null : data.first; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/utils/web_client.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:http/http.dart' as http; 3 | import 'package:invoiceninja/invoiceninja.dart'; 4 | 5 | /// Utility class to make web requests 6 | class WebClient { 7 | const WebClient(); 8 | 9 | /// Send a GET web request 10 | Future get(String url, {String? companyKey, String? token}) async { 11 | _checkInitialized(); 12 | 13 | final http.Response response = await http.Client().get( 14 | Uri.parse(url), 15 | headers: _getHeaders(companyKey: companyKey, token: token), 16 | ); 17 | 18 | if (InvoiceNinja.debugEnabled == true || 19 | InvoiceNinjaAdmin.debugEnabled == true) { 20 | _printWrapped('Invoice Ninja [GET] $url\n${response.body}'); 21 | } 22 | 23 | _checkResponse(response); 24 | 25 | return json.decode(response.body); 26 | } 27 | 28 | /// Send a POST web request 29 | Future post( 30 | String url, { 31 | String? companyKey, 32 | String? token, 33 | dynamic data, 34 | }) async { 35 | _checkInitialized(); 36 | 37 | final http.Response response = await http.Client() 38 | .post(Uri.parse(url), 39 | body: json.encode(data), 40 | headers: _getHeaders(companyKey: companyKey, token: token)) 41 | .timeout(const Duration(seconds: 60)); 42 | 43 | if (InvoiceNinja.debugEnabled == true || 44 | InvoiceNinjaAdmin.debugEnabled == true) { 45 | _printWrapped('Invoice Ninja [POST] $url\n${response.body}'); 46 | } 47 | 48 | _checkResponse(response); 49 | 50 | return json.decode(response.body); 51 | } 52 | 53 | /// Send a PUT web request 54 | Future put( 55 | String url, { 56 | String? companyKey, 57 | String? token, 58 | dynamic data, 59 | }) async { 60 | _checkInitialized(); 61 | 62 | final http.Response response = await http.Client() 63 | .put(Uri.parse(url), 64 | body: json.encode(data), 65 | headers: _getHeaders(companyKey: companyKey, token: token)) 66 | .timeout(const Duration(seconds: 60)); 67 | 68 | if (InvoiceNinja.debugEnabled == true || 69 | InvoiceNinjaAdmin.debugEnabled == true) { 70 | _printWrapped('Invoice Ninja [PUT] $url\n${response.body}'); 71 | } 72 | 73 | _checkResponse(response); 74 | 75 | return json.decode(response.body); 76 | } 77 | } 78 | 79 | /// Determine headers for request 80 | Map _getHeaders({String? companyKey, String? token}) { 81 | final data = { 82 | 'X-Requested-With': 'XMLHttpRequest', 83 | 'Content-Type': 'application/json', 84 | 'user-agent': 'flutter-package', 85 | }; 86 | 87 | if ((token ?? '').isNotEmpty) { 88 | data['X-API-Token'] = token ?? ''; 89 | } else if ((companyKey ?? '').isNotEmpty) { 90 | data['X-API-COMPANY-KEY'] = companyKey ?? ''; 91 | } else { 92 | throw 'Invoice Ninja error: the package is not initialized'; 93 | } 94 | 95 | return data; 96 | } 97 | 98 | /// Print long debug string 99 | void _printWrapped(String text) { 100 | final pattern = RegExp('.{1,800}'); // 800 is the size of each chunk 101 | pattern.allMatches(text).forEach((match) => print(match.group(0))); 102 | } 103 | 104 | /// Ensure package is correctly initialized 105 | void _checkInitialized() { 106 | if (!InvoiceNinja.isInitialized && !InvoiceNinjaAdmin.isInitialized) { 107 | throw 'Invoice Ninja error: the package is not initialized'; 108 | } 109 | } 110 | 111 | /// Ensure the response is valid 112 | void _checkResponse(http.Response response) { 113 | final serverVersion = response.headers['x-app-version']; 114 | 115 | if (serverVersion == null) { 116 | throw 'Invoice Ninja error: please check that v5 is installed on the server'; 117 | } else if (response.statusCode >= 400) { 118 | throw 'Invoice Ninja error: ${response.body}'; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: invoiceninja 2 | description: Easily create PDF invoices and accept payments in a Flutter app 3 | version: 0.0.11 4 | homepage: https://invoiceninja.com 5 | repository: https://github.com/invoiceninja/flutter-package 6 | issue_tracker: https://github.com/invoiceninja/flutter-package/issues 7 | 8 | environment: 9 | sdk: ">=2.12.0 <3.0.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | http: ^0.13.5 15 | freezed_annotation: ^2.2.0 16 | json_serializable: ^6.6.1 17 | json_annotation: ^4.8.0 18 | 19 | dev_dependencies: 20 | flutter_test: 21 | sdk: flutter 22 | build_runner: ^2.3.3 23 | freezed: ^2.3.2 24 | 25 | # For information on the generic Dart part of this file, see the 26 | # following page: https://dart.dev/tools/pub/pubspec 27 | 28 | # The following section is specific to Flutter. 29 | flutter: 30 | 31 | # To add assets to your package, add an assets section, like this: 32 | # assets: 33 | # - images/a_dot_burr.jpeg 34 | # - images/a_dot_ham.jpeg 35 | # 36 | # For details regarding assets in packages, see 37 | # https://flutter.dev/assets-and-images/#from-packages 38 | # 39 | # An image asset can refer to one or more resolution-specific "variants", see 40 | # https://flutter.dev/assets-and-images/#resolution-aware. 41 | 42 | # To add custom fonts to your package, add a fonts section here, 43 | # in this "flutter" section. Each entry in this list should have a 44 | # "family" key with the font family name, and a "fonts" key with a 45 | # list giving the asset and other descriptors for the font. For 46 | # example: 47 | # fonts: 48 | # - family: Schyler 49 | # fonts: 50 | # - asset: fonts/Schyler-Regular.ttf 51 | # - asset: fonts/Schyler-Italic.ttf 52 | # style: italic 53 | # - family: Trajan Pro 54 | # fonts: 55 | # - asset: fonts/TrajanPro.ttf 56 | # - asset: fonts/TrajanPro_Bold.ttf 57 | # weight: 700 58 | # 59 | # For details regarding fonts in packages, see 60 | # https://flutter.dev/custom-fonts/#from-packages 61 | -------------------------------------------------------------------------------- /test/invoiceninja_test.dart: -------------------------------------------------------------------------------- 1 | // Import the test package and Counter class 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:invoiceninja/invoiceninja.dart'; 4 | import 'package:invoiceninja/models/client.dart'; 5 | import 'package:invoiceninja/models/invoice.dart'; 6 | 7 | void main() { 8 | setUp(() async { 9 | InvoiceNinja.configure( 10 | 'KEY', 11 | url: 'demo.invoiceninja.com', 12 | debugEnabled: true, 13 | ); 14 | }); 15 | 16 | test('Test company', () async { 17 | final company = await InvoiceNinja.company.load(); 18 | expect(company.key, 'KEY'); 19 | }); 20 | 21 | test('Test products', () async { 22 | final products = await InvoiceNinja.products.load(); 23 | expect(products.isNotEmpty, true); 24 | 25 | final productKey = products.first.productKey; 26 | final product = await InvoiceNinja.products.findByKey(productKey); 27 | 28 | expect(productKey, product.productKey); 29 | }); 30 | 31 | test('Test clients', () async { 32 | var testEmail = '${DateTime.now().millisecondsSinceEpoch}@example.com'; 33 | var client = Client.forContact(email: testEmail); 34 | 35 | client = await InvoiceNinja.clients.save(client); 36 | expect(client.id.isNotEmpty, true); 37 | expect(client.contacts.first.email, testEmail); 38 | 39 | final key = client.key; 40 | client = await InvoiceNinja.clients.findByKey(key); 41 | expect(client.key, key); 42 | expect(client.contacts.first.email, testEmail); 43 | }); 44 | 45 | test('Test invoices', () async { 46 | final products = await InvoiceNinja.products.load(); 47 | var testEmail = '${DateTime.now().millisecondsSinceEpoch}@example.com'; 48 | var client = Client.forContact(email: testEmail); 49 | client = await InvoiceNinja.clients.save(client); 50 | expect(client.id.isNotEmpty, true); 51 | 52 | var invoice = Invoice.forClient(client, products: [products.first]); 53 | invoice = await InvoiceNinja.invoices.save(invoice); 54 | expect(invoice.id.isNotEmpty, true); 55 | 56 | final key = invoice.key; 57 | invoice = await InvoiceNinja.invoices.findByKey(key); 58 | expect(invoice.key, key); 59 | }); 60 | } 61 | --------------------------------------------------------------------------------