├── lib
├── redux
│ ├── app
│ │ ├── app_selectors.dart
│ │ └── loading_reducer.dart
│ ├── ui
│ │ ├── ui_actions.dart
│ │ └── entity_ui_state.dart
│ ├── reports
│ │ ├── reports_selectors.dart
│ │ ├── reports_actions.dart
│ │ ├── reports_state.dart
│ │ ├── reports_reducer.dart
│ │ └── reports_middleware.dart
│ ├── dashboard
│ │ ├── dashboard_actions.dart
│ │ └── dashboard_middleware.dart
│ └── auth
│ │ └── auth_state.dart
├── ui
│ ├── app
│ │ ├── upgrade_dialog.dart.foss
│ │ ├── snackbar_row.dart
│ │ ├── help_text.dart
│ │ ├── blank_screen.dart
│ │ ├── entities
│ │ │ └── entity_state_title.dart
│ │ ├── entity_state_label.dart
│ │ ├── dialogs
│ │ │ └── loading_dialog.dart
│ │ ├── icon_text.dart
│ │ ├── loading_indicator.dart
│ │ ├── buttons
│ │ │ ├── edit_icon_button.dart
│ │ │ └── app_text_button.dart
│ │ ├── progress_button.dart
│ │ ├── gateways
│ │ │ └── token_meta.dart
│ │ ├── lists
│ │ │ ├── list_divider.dart
│ │ │ └── selected_indicator.dart
│ │ ├── forms
│ │ │ ├── user_picker.dart
│ │ │ ├── app_tab_bar.dart
│ │ │ ├── help_link.dart
│ │ │ ├── app_toggle_buttons.dart
│ │ │ ├── growable_form_field.dart
│ │ │ ├── design_picker.dart
│ │ │ ├── save_cancel_buttons.dart
│ │ │ ├── app_form.dart
│ │ │ └── client_picker.dart
│ │ ├── live_text.dart
│ │ ├── icon_message.dart
│ │ ├── history_drawer_vm.dart
│ │ ├── resources
│ │ │ └── cached_image.dart
│ │ ├── responsive_padding.dart
│ │ ├── copy_to_clipboard.dart
│ │ ├── web_session_timeout.dart
│ │ └── invoice
│ │ │ └── tax_rate_field.dart
│ ├── dashboard
│ │ ├── dashboard_overview.dart
│ │ ├── dashboard_system_logs.dart
│ │ └── dashboard_activity.dart
│ ├── task
│ │ └── view
│ │ │ └── task_view_documents.dart
│ ├── client
│ │ ├── view
│ │ │ ├── client_view_documents.dart
│ │ │ ├── client_view_system_logs.dart
│ │ │ └── client_view_activity.dart
│ │ └── client_pdf_vm.dart
│ ├── product
│ │ └── view
│ │ │ └── product_view_documents.dart
│ ├── vendor
│ │ └── view
│ │ │ └── vendor_view_documents.dart
│ ├── project
│ │ └── view
│ │ │ └── project_view_documents.dart
│ ├── design
│ │ └── design_presenter.dart
│ ├── payment_term
│ │ ├── payment_term_presenter.dart
│ │ └── view
│ │ │ └── payment_term_view.dart
│ ├── purchase_order
│ │ ├── purchase_order_presenter.dart
│ │ └── view
│ │ │ └── purchase_order_view.dart
│ ├── token
│ │ └── token_presenter.dart
│ ├── webhook
│ │ └── webhook_presenter.dart
│ ├── task_status
│ │ └── task_status_presenter.dart
│ ├── subscription
│ │ └── subscription_presenter.dart
│ ├── expense
│ │ └── view
│ │ │ └── expense_view_documents.dart
│ ├── expense_category
│ │ └── expense_category_presenter.dart
│ ├── invoice
│ │ ├── view
│ │ │ ├── invoice_view_documents.dart
│ │ │ └── invoice_view_activity.dart
│ │ └── edit
│ │ │ └── invoice_edit_pdf_vm.dart
│ ├── document
│ │ ├── view
│ │ │ └── document_view.dart
│ │ └── document_screen_vm.dart
│ ├── settings
│ │ ├── settings_screen_vm.dart
│ │ ├── import_export_vm.dart
│ │ ├── data_visualizations_vm.dart
│ │ ├── credit_cards_and_banks_vm.dart
│ │ └── settings_screen.dart
│ ├── auth
│ │ ├── init_screen.dart
│ │ └── lock_screen.dart
│ ├── quote
│ │ ├── edit
│ │ │ ├── quote_edit_pdf_vm.dart
│ │ │ └── quote_edit_notes_vm.dart
│ │ └── quote_pdf_vm.dart
│ ├── credit
│ │ ├── edit
│ │ │ ├── credit_edit_pdf_vm.dart
│ │ │ └── credit_edit_notes_vm.dart
│ │ └── credit_pdf_vm.dart
│ ├── tax_rate
│ │ └── view
│ │ │ └── tax_rate_view.dart
│ ├── recurring_invoice
│ │ └── edit
│ │ │ └── recurring_invoice_edit_pdf_vm.dart
│ └── user
│ │ └── user_screen_vm.dart
├── utils
│ ├── serialization.dart
│ ├── enums.dart
│ ├── contacts.dart
│ ├── oauth.dart.foss
│ ├── colors.dart
│ ├── extensions.dart
│ ├── web_stub.dart
│ ├── money.dart
│ ├── localization.dart
│ └── designs.dart
├── flutter_version.dart
├── .env.dart.example
└── data
│ ├── repositories
│ └── static
│ │ └── static_repository.dart
│ └── models
│ ├── static
│ └── font_model.dart
│ ├── recurring_invoice_model.dart
│ └── quote_model.dart
├── linux
├── .gitignore
├── main.cc
├── flutter
│ ├── generated_plugin_registrant.h
│ ├── generated_plugins.cmake
│ └── generated_plugin_registrant.cc
└── my_application.h
├── fastlane
└── metadata
│ └── android
│ └── en-US
│ ├── title.txt
│ ├── short_description.txt
│ ├── images
│ ├── icon.png
│ └── phoneScreenshots
│ │ ├── 1.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ └── 4.png
│ └── full_description.txt
├── ios
├── Runner
│ ├── Runner-Bridging-Header.h
│ ├── Assets.xcassets
│ │ ├── LaunchImage.imageset
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ ├── README.md
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x-1.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x-1.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x-1.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x-1.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ └── Icon-App-83.5x83.5@2x.png
│ ├── AppDelegate.swift
│ └── Base.lproj
│ │ └── Main.storyboard
├── Flutter
│ ├── Debug.xcconfig
│ ├── Release.xcconfig
│ └── AppFrameworkInfo.plist
├── Runner.xcodeproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ ├── WorkspaceSettings.xcsettings
│ │ └── IDEWorkspaceChecks.plist
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── WorkspaceSettings.xcsettings
│ │ └── IDEWorkspaceChecks.plist
└── .gitignore
├── web
├── favicon.ico
├── favicon.png
├── icons
│ ├── Icon-192.png
│ └── Icon-512.png
└── manifest.json
├── run_all_it_tests.sh
├── assets
└── images
│ ├── icon.png
│ ├── google_logo.png
│ ├── logo_dark.png
│ ├── logo_light.png
│ ├── mobile_icon.png
│ └── payment_types
│ ├── ach.png
│ ├── amex.png
│ ├── jcb.png
│ ├── solo.png
│ ├── visa.png
│ ├── laser.png
│ ├── maestro.png
│ ├── other.png
│ ├── paypal.png
│ ├── switch.png
│ ├── discover.png
│ ├── unionpay.png
│ ├── carteblanche.png
│ ├── dinerscard.png
│ └── mastercard.png
├── samples
└── screenshots
│ ├── 1.png
│ ├── 2.png
│ ├── 3.png
│ └── 4.png
├── snap
├── gui
│ ├── invoiceninja.png
│ └── invoiceninja.desktop
└── snapcraft.yaml
├── android
├── key.properties.example
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── app
│ └── src
│ │ ├── main
│ │ ├── res
│ │ │ ├── drawable
│ │ │ │ ├── logo.png
│ │ │ │ └── launch_background.xml
│ │ │ ├── drawable-v21
│ │ │ │ ├── logo.png
│ │ │ │ └── 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
│ │ │ │ ├── colors.xml
│ │ │ │ └── styles.xml
│ │ │ ├── drawable-hdpi
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── drawable-mdpi
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── drawable-xhdpi
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── drawable-xxhdpi
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── drawable-xxxhdpi
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ └── ic_launcher.xml
│ │ │ └── values-night
│ │ │ │ └── styles.xml
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── invoiceninja
│ │ │ └── app
│ │ │ └── MainActivity.kt
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── gradle.properties
├── .gitignore
├── settings.gradle
├── build.gradle.foss
└── build.gradle
├── macos
├── Runner
│ ├── Configs
│ │ ├── Debug.xcconfig
│ │ ├── Release.xcconfig
│ │ ├── Warnings.xcconfig
│ │ └── AppInfo.xcconfig
│ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ ├── app_icon_128.png
│ │ │ ├── app_icon_16.png
│ │ │ ├── app_icon_256.png
│ │ │ ├── app_icon_32.png
│ │ │ ├── app_icon_512.png
│ │ │ ├── app_icon_64.png
│ │ │ ├── app_icon_1024.png
│ │ │ └── Contents.json
│ ├── AppDelegate.swift
│ ├── MainFlutterWindow.swift
│ ├── Release.entitlements
│ └── DebugProfile.entitlements
├── .gitignore
├── Flutter
│ ├── Flutter-Debug.xcconfig
│ ├── Flutter-Release.xcconfig
│ └── GeneratedPluginRegistrant.swift
├── Runner.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── Runner.xcodeproj
│ └── project.xcworkspace
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── Podfile
├── windows
├── runner
│ ├── resources
│ │ └── app_icon.ico
│ ├── resource.h
│ ├── utils.h
│ ├── runner.exe.manifest
│ ├── flutter_window.h
│ ├── CMakeLists.txt
│ ├── main.cpp
│ └── utils.cpp
├── .gitignore
└── flutter
│ ├── generated_plugin_registrant.h
│ ├── generated_plugin_registrant.cc
│ └── generated_plugins.cmake
├── test_driver
├── utils
│ └── localizations.dart
├── all_it.dart
├── clients_it.dart
├── invoices_it.dart
├── login_it.dart
├── products_it.dart
├── quotes_it.dart
├── vendors_it.dart
└── all_it_test.dart
├── stubs
└── ui
│ └── stub
│ ├── stub_presenter
│ └── view
│ └── stub_view
├── .metadata
├── invoiceninja_android.iml
└── .github
└── workflows
└── test.yml
/lib/redux/app/app_selectors.dart:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/linux/.gitignore:
--------------------------------------------------------------------------------
1 | flutter/ephemeral
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/title.txt:
--------------------------------------------------------------------------------
1 | Invoice Ninja
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/web/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/web/favicon.ico
--------------------------------------------------------------------------------
/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/web/favicon.png
--------------------------------------------------------------------------------
/run_all_it_tests.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | flutter drive --target=test_driver/all_it.dart
--------------------------------------------------------------------------------
/assets/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/assets/images/icon.png
--------------------------------------------------------------------------------
/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/samples/screenshots/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/samples/screenshots/1.png
--------------------------------------------------------------------------------
/samples/screenshots/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/samples/screenshots/2.png
--------------------------------------------------------------------------------
/samples/screenshots/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/samples/screenshots/3.png
--------------------------------------------------------------------------------
/samples/screenshots/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/samples/screenshots/4.png
--------------------------------------------------------------------------------
/snap/gui/invoiceninja.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/snap/gui/invoiceninja.png
--------------------------------------------------------------------------------
/assets/images/google_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/assets/images/google_logo.png
--------------------------------------------------------------------------------
/assets/images/logo_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/assets/images/logo_dark.png
--------------------------------------------------------------------------------
/assets/images/logo_light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/assets/images/logo_light.png
--------------------------------------------------------------------------------
/assets/images/mobile_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/assets/images/mobile_icon.png
--------------------------------------------------------------------------------
/android/key.properties.example:
--------------------------------------------------------------------------------
1 | storePassword=
2 | keyPassword=
3 | keyAlias=key
4 | storeFile=c:/Users/username/key.jks
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/short_description.txt:
--------------------------------------------------------------------------------
1 | Create invoices, accept payments, track expenses & time-tasks
--------------------------------------------------------------------------------
/macos/Runner/Configs/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Debug.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/assets/images/payment_types/ach.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/assets/images/payment_types/ach.png
--------------------------------------------------------------------------------
/assets/images/payment_types/amex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/assets/images/payment_types/amex.png
--------------------------------------------------------------------------------
/assets/images/payment_types/jcb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/assets/images/payment_types/jcb.png
--------------------------------------------------------------------------------
/assets/images/payment_types/solo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/assets/images/payment_types/solo.png
--------------------------------------------------------------------------------
/assets/images/payment_types/visa.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/assets/images/payment_types/visa.png
--------------------------------------------------------------------------------
/macos/Runner/Configs/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Release.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/assets/images/payment_types/laser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/assets/images/payment_types/laser.png
--------------------------------------------------------------------------------
/assets/images/payment_types/maestro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/assets/images/payment_types/maestro.png
--------------------------------------------------------------------------------
/assets/images/payment_types/other.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/assets/images/payment_types/other.png
--------------------------------------------------------------------------------
/assets/images/payment_types/paypal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/assets/images/payment_types/paypal.png
--------------------------------------------------------------------------------
/assets/images/payment_types/switch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/assets/images/payment_types/switch.png
--------------------------------------------------------------------------------
/macos/.gitignore:
--------------------------------------------------------------------------------
1 | # Flutter-related
2 | **/Flutter/ephemeral/
3 | **/Pods/
4 |
5 | # Xcode-related
6 | **/dgph
7 | **/xcuserdata/
8 |
--------------------------------------------------------------------------------
/windows/runner/resources/app_icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/windows/runner/resources/app_icon.ico
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/assets/images/payment_types/discover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/assets/images/payment_types/discover.png
--------------------------------------------------------------------------------
/assets/images/payment_types/unionpay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/assets/images/payment_types/unionpay.png
--------------------------------------------------------------------------------
/lib/redux/ui/ui_actions.dart:
--------------------------------------------------------------------------------
1 | class UpdateCurrentRoute {
2 | UpdateCurrentRoute(this.route);
3 |
4 | final String route;
5 | }
6 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/android/app/src/main/res/drawable/logo.png
--------------------------------------------------------------------------------
/assets/images/payment_types/carteblanche.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/assets/images/payment_types/carteblanche.png
--------------------------------------------------------------------------------
/assets/images/payment_types/dinerscard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/assets/images/payment_types/dinerscard.png
--------------------------------------------------------------------------------
/assets/images/payment_types/mastercard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/assets/images/payment_types/mastercard.png
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/android/app/src/main/res/drawable-v21/logo.png
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/fastlane/metadata/android/en-US/images/icon.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/macos/Flutter/Flutter-Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "ephemeral/Flutter-Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/macos/Flutter/Flutter-Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "ephemeral/Flutter-Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 | android.enableR8=true
5 | android.enableDexingArtifactTransform=false
6 |
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x-1.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rwema3/admin-portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/snap/gui/invoiceninja.desktop:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Name=Invoice Ninja
3 | Comment=Online Invoicing
4 | Exec=invoiceninja
5 | Icon=${SNAP}/meta/gui/invoiceninja.png
6 | Terminal=false
7 | Type=Application
8 | Categories=Office;
--------------------------------------------------------------------------------
/test_driver/utils/localizations.dart:
--------------------------------------------------------------------------------
1 | // Project imports:
2 | import 'package:invoiceninja_flutter/utils/i18n.dart';
3 |
4 | class TestLocalization extends LocaleCodeAware with LocalizationsProvider {
5 | TestLocalization(String localeCode) : super(localeCode);
6 | }
7 |
--------------------------------------------------------------------------------
/macos/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | @NSApplicationMain
5 | class AppDelegate: FlutterAppDelegate {
6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
7 | return true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/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-7.2-all.zip
7 |
--------------------------------------------------------------------------------
/test_driver/all_it.dart:
--------------------------------------------------------------------------------
1 | // Package imports:
2 | import 'package:flutter_driver/driver_extension.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/main.dart' as app;
6 |
7 | void main() {
8 | enableFlutterDriverExtension();
9 | app.main(isTesting: true);
10 | }
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/macos/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/test_driver/clients_it.dart:
--------------------------------------------------------------------------------
1 | // Package imports:
2 | import 'package:flutter_driver/driver_extension.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/main.dart' as app;
6 |
7 | void main() {
8 | // This line enables the extension
9 | enableFlutterDriverExtension();
10 | app.main(isTesting: true);
11 | }
12 |
--------------------------------------------------------------------------------
/test_driver/invoices_it.dart:
--------------------------------------------------------------------------------
1 | // Package imports:
2 | import 'package:flutter_driver/driver_extension.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/main.dart' as app;
6 |
7 | void main() {
8 | // This line enables the extension
9 | enableFlutterDriverExtension();
10 | app.main(isTesting: true);
11 | }
12 |
--------------------------------------------------------------------------------
/test_driver/login_it.dart:
--------------------------------------------------------------------------------
1 | // Package imports:
2 | import 'package:flutter_driver/driver_extension.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/main.dart' as app;
6 |
7 | void main() {
8 | // This line enables the extension
9 | enableFlutterDriverExtension();
10 | app.main(isTesting: true);
11 | }
12 |
--------------------------------------------------------------------------------
/test_driver/products_it.dart:
--------------------------------------------------------------------------------
1 | // Package imports:
2 | import 'package:flutter_driver/driver_extension.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/main.dart' as app;
6 |
7 | void main() {
8 | // This line enables the extension
9 | enableFlutterDriverExtension();
10 | app.main(isTesting: true);
11 | }
12 |
--------------------------------------------------------------------------------
/test_driver/quotes_it.dart:
--------------------------------------------------------------------------------
1 | // Package imports:
2 | import 'package:flutter_driver/driver_extension.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/main.dart' as app;
6 |
7 | void main() {
8 | // This line enables the extension
9 | enableFlutterDriverExtension();
10 | app.main(isTesting: true);
11 | }
12 |
--------------------------------------------------------------------------------
/test_driver/vendors_it.dart:
--------------------------------------------------------------------------------
1 | // Package imports:
2 | import 'package:flutter_driver/driver_extension.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/main.dart' as app;
6 |
7 | void main() {
8 | // This line enables the extension
9 | enableFlutterDriverExtension();
10 | app.main(isTesting: true);
11 | }
12 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/ui/app/upgrade_dialog.dart.foss:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class UpgradeDialog extends StatefulWidget {
4 | @override
5 | _UpgradeDialogState createState() => _UpgradeDialogState();
6 | }
7 |
8 | class _UpgradeDialogState extends State {
9 | @override
10 | Widget build(BuildContext context) {
11 | return Container();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/redux/reports/reports_selectors.dart:
--------------------------------------------------------------------------------
1 | import 'package:invoiceninja_flutter/constants.dart';
2 | import 'package:invoiceninja_flutter/data/models/company_model.dart';
3 |
4 | dynamic presentCustomField(
5 | {String value, String customFieldType, CompanyEntity company}) {
6 | if (company.getCustomFieldType(customFieldType) == kFieldTypeSwitch)
7 | return value == 'yes';
8 | else
9 | return value;
10 | }
11 |
--------------------------------------------------------------------------------
/lib/utils/serialization.dart:
--------------------------------------------------------------------------------
1 | // Project imports:
2 | import 'package:invoiceninja_flutter/data/models/serializers.dart';
3 |
4 | class SerializationUtils {
5 | static dynamic computeDecode(dynamic list) {
6 | return serializers.deserializeWith(list[0], list[1]);
7 | }
8 |
9 | static dynamic computeEncode(dynamic list) {
10 | return serializers.serializeWith(list[0], list[1]);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/macos/Runner/MainFlutterWindow.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | class MainFlutterWindow: NSWindow {
5 | override func awakeFromNib() {
6 | let flutterViewController = FlutterViewController.init()
7 | let windowFrame = self.frame
8 | self.contentViewController = flutterViewController
9 | self.setFrame(windowFrame, display: true)
10 |
11 | RegisterGeneratedPlugins(registry: flutterViewController)
12 |
13 | super.awakeFromNib()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/macos/Runner/Release.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.downloads.read-write
8 |
9 | com.apple.security.network.client
10 |
11 | com.apple.security.print
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | -
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | -
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
4 | def properties = new Properties()
5 |
6 | assert localPropertiesFile.exists()
7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
8 |
9 | def flutterSdkPath = properties.getProperty("flutter.sdk")
10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
12 |
--------------------------------------------------------------------------------
/lib/flutter_version.dart:
--------------------------------------------------------------------------------
1 | const FLUTTER_VERSION = const {
2 | 'frameworkVersion': '1.26.0-17.2.pre',
3 | 'channel': 'dev',
4 | 'repositoryUrl': 'git@github.com:flutter/flutter.git',
5 | 'frameworkRevision': '79b49b9e1057f90ebf797725233c6b311722de69',
6 | 'frameworkCommitDate': '2021-02-03 15:33:39 -0800',
7 | 'engineRevision': '2c527d6c7e70e2f51bca1a46f1174b250f84c5da',
8 | 'dartSdkVersion': '2.12.0 (build 2.12.0-259.8.beta)',
9 | 'flutterRoot': 'C:\\Users\\hillel\\Documents\\flutter'
10 | };
11 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/invoiceninja/app/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.invoiceninja.app
2 |
3 | import androidx.annotation.NonNull;
4 | import io.flutter.embedding.android.FlutterFragmentActivity
5 | import io.flutter.embedding.engine.FlutterEngine
6 | import io.flutter.plugins.GeneratedPluginRegistrant
7 |
8 | class MainActivity: FlutterFragmentActivity() {
9 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
10 | GeneratedPluginRegistrant.registerWith(flutterEngine);
11 | }
12 | }
--------------------------------------------------------------------------------
/lib/ui/dashboard/dashboard_overview.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/ui/dashboard/dashboard_screen_vm.dart';
6 |
7 | class DashboardOverview extends StatelessWidget {
8 | const DashboardOverview({
9 | Key key,
10 | @required this.viewModel,
11 | }) : super(key: key);
12 |
13 | final DashboardVM viewModel;
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | return new Container();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/utils/enums.dart:
--------------------------------------------------------------------------------
1 | class EnumUtils {
2 | static String parse(dynamic enumItem) {
3 | if (enumItem == null) {
4 | return null;
5 | }
6 | return enumItem.toString().split('.')[1];
7 | }
8 |
9 | static T fromString(List enumValues, String value) {
10 | if (value == null || enumValues == null) {
11 | return null;
12 | }
13 |
14 | return enumValues.singleWhere(
15 | (enumItem) =>
16 | EnumUtils.parse(enumItem)?.toLowerCase() == value?.toLowerCase(),
17 | orElse: () => null);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib/.env.dart.example:
--------------------------------------------------------------------------------
1 | class Config {
2 | static const String API_SECRET = '';
3 | static const String SENTRY_DNS = 'https://21e3763afb044f1fb75d832fd9f06b73@sentry2.invoicing.co/3';
4 |
5 | static const bool DEBUG_EVENTS = false;
6 | static const bool DEBUG_REQUESTS = false;
7 | static const bool DEMO_MODE = false;
8 |
9 | static const String TEST_EMAIL = 'demo@invoiceninja.com';
10 | static const String TEST_PASSWORD = 'Password0';
11 | static const String TEST_URL = 'https://demo.invoiceninja.com';
12 | static const String TEST_SECRET = '';
13 | }
14 |
--------------------------------------------------------------------------------
/test_driver/all_it_test.dart:
--------------------------------------------------------------------------------
1 | // Project imports:
2 | import 'clients_it_test.dart' as clients;
3 | import 'invoices_it_test.dart' as invoices;
4 | import 'login_it_test.dart' as login;
5 | import 'products_it_test.dart' as products;
6 | import 'quotes_it_test.dart' as quotes;
7 | import 'vendors_it_test.dart' as vendors;
8 |
9 | void main() {
10 | login.main();
11 | products.runTestSuite(batchMode: true);
12 | clients.runTestSuite(batchMode: true);
13 | invoices.runTestSuite(batchMode: true);
14 | quotes.runTestSuite(batchMode: true);
15 | vendors.runTestSuite(batchMode: true);
16 | }
17 |
--------------------------------------------------------------------------------
/lib/ui/app/snackbar_row.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | class SnackBarRow extends StatelessWidget {
5 | const SnackBarRow({
6 | this.message,
7 | this.icon = Icons.check_circle,
8 | });
9 |
10 | final String message;
11 | final IconData icon;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Row(
16 | children: [
17 | Icon(icon),
18 | Padding(
19 | padding: EdgeInsets.only(left: 10.0),
20 | child: Text(message),
21 | )
22 | ],
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/full_description.txt:
--------------------------------------------------------------------------------
1 | Invoice Ninja is an invoicing application that makes sending invoices and receiving payments simple and easy. Our latest version is a clean slate rewrite of our popular invoicing application which builds on the existing feature set and adds a wide range of features and enhancements that you have asked for.
2 |
3 | We're similar to WordPress, you can either run the app on our server (hosted) or on your own server (selfhosted) – the server-part is built with PHP/Laravel; many people use Docker or Cloudron to install a packaged version, but you also can install it manually.
4 |
--------------------------------------------------------------------------------
/macos/Runner/DebugProfile.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.cs.allow-jit
8 |
9 | com.apple.security.files.downloads.read-write
10 |
11 | com.apple.security.network.client
12 |
13 | com.apple.security.network.server
14 |
15 | com.apple.security.print
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/lib/ui/app/help_text.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | class HelpText extends StatelessWidget {
5 | const HelpText(this.message);
6 |
7 | final String message;
8 |
9 | @override
10 | Widget build(BuildContext context) {
11 | return Container(
12 | child: Center(
13 | child: Opacity(
14 | opacity: 0.8,
15 | child: Text(
16 | message,
17 | style: TextStyle(
18 | fontSize: 22,
19 | color: Colors.grey,
20 | ),
21 | ),
22 | ),
23 | ),
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/Warnings.xcconfig:
--------------------------------------------------------------------------------
1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
2 | GCC_WARN_UNDECLARED_SELECTOR = YES
3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
6 | CLANG_WARN_PRAGMA_PACK = YES
7 | CLANG_WARN_STRICT_PROTOTYPES = YES
8 | CLANG_WARN_COMMA = YES
9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES
10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
12 | GCC_WARN_SHADOW = YES
13 | CLANG_WARN_UNREACHABLE_CODE = YES
14 |
--------------------------------------------------------------------------------
/macos/Runner/Configs/AppInfo.xcconfig:
--------------------------------------------------------------------------------
1 | // Application-level settings for the Runner target.
2 | //
3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
4 | // future. If not, the values below would default to using the project name when this becomes a
5 | // 'flutter create' template.
6 |
7 | // The application's name. By default this is also the title of the Flutter window.
8 | PRODUCT_NAME = Invoice Ninja
9 |
10 | // The application's bundle identifier
11 | PRODUCT_BUNDLE_IDENTIFIER = com.invoiceninja.app
12 |
13 | // The copyright displayed in application information
14 | PRODUCT_COPYRIGHT = Copyright © 2021 Invoice Ninja All rights reserved.
15 |
--------------------------------------------------------------------------------
/lib/utils/contacts.dart:
--------------------------------------------------------------------------------
1 | // Package imports:
2 | import 'package:contacts_service/contacts_service.dart';
3 | import 'package:permission_handler/permission_handler.dart';
4 |
5 | Future getDeviceContact() async {
6 | try {
7 | final permissionStatus = await Permission.contacts.request();
8 | if (permissionStatus == PermissionStatus.granted) {
9 | return await ContactsService.openDeviceContactPicker();
10 | } else if ([PermissionStatus.denied, PermissionStatus.permanentlyDenied]
11 | .contains(permissionStatus)) {
12 | openAppSettings();
13 | }
14 | } catch (e) {
15 | print('## ERROR: failed to get contact: $e');
16 | }
17 |
18 | return null;
19 | }
20 |
--------------------------------------------------------------------------------
/lib/utils/oauth.dart.foss:
--------------------------------------------------------------------------------
1 | class GoogleOAuth {
2 |
3 | static bool get isEnabled => false;
4 |
5 | static Future signIn(Function(String, String) callback, {bool isSilent = false}) async {
6 | //
7 | }
8 |
9 | static Future signUp(Function(String, String) callback) async {
10 | //
11 | }
12 |
13 | static Future requestGmailScope() async {
14 | //
15 | }
16 |
17 | /*
18 | static Future grantOfflineAccess(Function(String, String, String) successCallback, Function errorCallback) async {
19 | //
20 | }
21 | */
22 |
23 | static void signOut() async {
24 | //
25 | }
26 |
27 | static void disconnect() async {
28 | //
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/android/build.gradle.foss:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.6.10'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:4.1.0'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | mavenCentral()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | project.evaluationDependsOn(':app')
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/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 | #include
11 | #include
12 |
13 | void RegisterPlugins(flutter::PluginRegistry* registry) {
14 | PrintingPluginRegisterWithRegistrar(
15 | registry->GetRegistrarForPlugin("PrintingPlugin"));
16 | SentryFlutterPluginRegisterWithRegistrar(
17 | registry->GetRegistrarForPlugin("SentryFlutterPlugin"));
18 | UrlLauncherWindowsRegisterWithRegistrar(
19 | registry->GetRegistrarForPlugin("UrlLauncherWindows"));
20 | }
21 |
--------------------------------------------------------------------------------
/lib/ui/app/blank_screen.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/ui/app/help_text.dart';
6 | import 'package:invoiceninja_flutter/utils/platforms.dart';
7 |
8 | class BlankScreen extends StatelessWidget {
9 | const BlankScreen([this.message]);
10 |
11 | final String message;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | return Scaffold(
16 | appBar: AppBar(
17 | centerTitle: false,
18 | automaticallyImplyLeading: isMobile(context),
19 | ),
20 | body: Container(
21 | color: Theme.of(context).cardColor,
22 | child: HelpText(message ?? ''),
23 | ),
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lib/ui/dashboard/dashboard_system_logs.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/ui/app/system_log_viewer.dart';
6 | import 'package:invoiceninja_flutter/ui/dashboard/dashboard_screen_vm.dart';
7 |
8 | class DashboardSystemLogs extends StatelessWidget {
9 | const DashboardSystemLogs({
10 | Key key,
11 | @required this.viewModel,
12 | }) : super(key: key);
13 |
14 | final DashboardVM viewModel;
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | final company = viewModel.state.company;
19 | final systemLogs = company.systemLogs;
20 |
21 | return SystemLogViewer(
22 | systemLogs: systemLogs,
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/ui/app/entities/entity_state_title.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/data/models/entities.dart';
6 | import 'package:invoiceninja_flutter/ui/app/presenters/entity_presenter.dart';
7 |
8 | class EntityStateTitle extends StatelessWidget {
9 | const EntityStateTitle({@required this.entity});
10 |
11 | final BaseEntity entity;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | String titleText = '';
16 | if (entity.isOld) {
17 | titleText = EntityPresenter().initialize(entity, context).title();
18 | }
19 |
20 | return Text(
21 | titleText,
22 | maxLines: 2,
23 | overflow: TextOverflow.ellipsis,
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.6.10'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:7.1.2'
10 | classpath 'com.google.gms:google-services:4.3.10'
11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | google()
18 | mavenCentral()
19 | }
20 | }
21 |
22 | rootProject.buildDir = '../build'
23 | subprojects {
24 | project.buildDir = "${rootProject.buildDir}/${project.name}"
25 | }
26 | subprojects {
27 | project.evaluationDependsOn(':app')
28 | }
29 |
30 | task clean(type: Delete) {
31 | delete rootProject.buildDir
32 | }
33 |
--------------------------------------------------------------------------------
/lib/utils/colors.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | Color convertHexStringToColor(String value) {
5 | if (value == null) {
6 | return null;
7 | }
8 | value = value.replaceAll('#', '');
9 | if (value.length != 6) {
10 | return null;
11 | }
12 | try {
13 | return Color(int.parse(value, radix: 16) + 0xFF000000);
14 | } catch (e) {
15 | return null;
16 | }
17 | }
18 |
19 | String convertColorToHexString(Color color) {
20 | try {
21 | final hex = color.value.toRadixString(16);
22 | return '#' + hex.substring(2, hex.length);
23 | } catch (e) {
24 | return null;
25 | }
26 | }
27 |
28 | Color getColorByIndex(int index) {
29 | final colorIndex = index % Colors.primaries.length;
30 | return Colors.primaries[colorIndex];
31 | }
32 |
--------------------------------------------------------------------------------
/lib/redux/ui/entity_ui_state.dart:
--------------------------------------------------------------------------------
1 | // Dart imports:
2 | import 'dart:async';
3 |
4 | // Package imports:
5 | import 'package:built_value/built_value.dart';
6 |
7 | // Project imports:
8 | import 'package:invoiceninja_flutter/data/models/entities.dart';
9 | import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart';
10 |
11 | abstract class EntityUIState {
12 | bool get isCreatingNew;
13 |
14 | String get editingId;
15 |
16 | ListUIState get listUIState;
17 |
18 | @nullable
19 | String get selectedId;
20 |
21 | @nullable
22 | bool get forceSelected;
23 |
24 | int get tabIndex;
25 |
26 | @nullable
27 | @BuiltValueField(serialize: false)
28 | Completer get saveCompleter;
29 |
30 | @nullable
31 | @BuiltValueField(serialize: false)
32 | Completer get cancelCompleter;
33 | }
34 |
--------------------------------------------------------------------------------
/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Invoice Ninja",
3 | "short_name": "Invoice Ninja",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "background_color": "#0175C2",
7 | "theme_color": "#0175C2",
8 | "description": "Invoice Clients, Track Work-Time, Get Paid Online.",
9 | "orientation": "portrait-primary",
10 | "prefer_related_applications": true,
11 | "related_applications": [
12 | {
13 | "platform": "play",
14 | "id": "com.invoiceninja.app"
15 | }, {
16 | "platform": "itunes",
17 | "url": "https://testflight.apple.com/join/MJ6WpaXh"
18 | }
19 | ],
20 | "icons": [
21 | {
22 | "src": "images/logo.png",
23 | "sizes": "512x512",
24 | "type": "image/png"
25 | }
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/lib/utils/extensions.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Package imports:
5 | import 'package:flutter_redux/flutter_redux.dart';
6 | import 'package:redux/redux.dart';
7 |
8 | // Project imports:
9 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
10 | import 'package:invoiceninja_flutter/utils/localization.dart';
11 |
12 | extension ContextHelper on BuildContext {
13 | AppLocalization get localization {
14 | return AppLocalization.of(this);
15 | }
16 |
17 | Store get store {
18 | return StoreProvider.of(this);
19 | }
20 |
21 | AppState get state {
22 | return store.state;
23 | }
24 | }
25 |
26 | extension ListHelper on List {
27 | T get firstOrNull => isEmpty ? null : first;
28 |
29 | T get lastOrNull => isEmpty ? null : last;
30 | }
31 |
--------------------------------------------------------------------------------
/lib/utils/web_stub.dart:
--------------------------------------------------------------------------------
1 | // Dart imports:
2 | import 'dart:typed_data';
3 |
4 | // Package imports:
5 | import 'package:redux/redux.dart';
6 |
7 | // Project imports:
8 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
9 |
10 | class WebUtils {
11 | static String get browserUrl => null;
12 |
13 | static String get browserRoute => null;
14 |
15 | static String getHtmlValue(String field) => null;
16 |
17 | static void downloadTextFile(String filename, String data) {}
18 |
19 | static void downloadBinaryFile(String filename, Uint8List data) {}
20 |
21 | static void reloadBrowser() {}
22 |
23 | static void registerWebView(String html) {}
24 |
25 | static void warnChanges(Store store) {}
26 |
27 | /*
28 | static String loadToken() => null;
29 |
30 | static void saveToken(String token) {}
31 | */
32 | }
33 |
--------------------------------------------------------------------------------
/lib/redux/app/loading_reducer.dart:
--------------------------------------------------------------------------------
1 | // Package imports:
2 | import 'package:redux/redux.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
6 |
7 | final loadingReducer = combineReducers([
8 | TypedReducer(_setLoading),
9 | TypedReducer(_setLoaded),
10 | ]);
11 |
12 | bool _setLoading(bool state, StartLoading action) {
13 | return true;
14 | }
15 |
16 | bool _setLoaded(bool state, StopLoading action) {
17 | return false;
18 | }
19 |
20 | final savingReducer = combineReducers([
21 | TypedReducer(_setSaving),
22 | TypedReducer(_setSaved),
23 | ]);
24 |
25 | bool _setSaving(bool state, StartSaving action) {
26 | return true;
27 | }
28 |
29 | bool _setSaved(bool state, StopSaving action) {
30 | return false;
31 | }
32 |
--------------------------------------------------------------------------------
/lib/ui/app/entity_state_label.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/data/models/models.dart';
6 | import 'package:invoiceninja_flutter/utils/localization.dart';
7 |
8 | class EntityStateLabel extends StatelessWidget {
9 | const EntityStateLabel(this.entity);
10 |
11 | final BaseEntity entity;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | final localization = AppLocalization.of(context);
16 |
17 | return entity.isDeleted
18 | ? Text(localization.deleted,
19 | style: TextStyle(color: Colors.red, fontSize: 14.0))
20 | : entity.isArchived
21 | ? Text(localization.archived,
22 | style: TextStyle(color: Colors.orange, fontSize: 14.0))
23 | : Container();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/lib/ui/app/dialogs/loading_dialog.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/utils/localization.dart';
6 |
7 | class LoadingDialog extends StatelessWidget {
8 | @override
9 | Widget build(BuildContext context) {
10 | final localization = AppLocalization.of(context);
11 |
12 | return Column(
13 | crossAxisAlignment: CrossAxisAlignment.start,
14 | children: [
15 | Padding(
16 | padding: const EdgeInsets.all(16.0),
17 | child: Text('${localization.loading}...'),
18 | ),
19 | Padding(
20 | padding: const EdgeInsets.all(16.0),
21 | child: SizedBox(
22 | height: 4.0,
23 | child: LinearProgressIndicator(),
24 | ))
25 | ],
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/lib/ui/task/view/task_view_documents.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/ui/app/document_grid.dart';
6 | import 'package:invoiceninja_flutter/ui/task/view/task_view_vm.dart';
7 |
8 | class TaskViewDocuments extends StatelessWidget {
9 | const TaskViewDocuments({Key key, @required this.viewModel})
10 | : super(key: key);
11 |
12 | final TaskViewVM viewModel;
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | final task = viewModel.task;
17 |
18 | return DocumentGrid(
19 | documents: task.documents.toList(),
20 | onUploadDocument: (path) => viewModel.onUploadDocument(context, path),
21 | onDeleteDocument: (document, password, idToken) =>
22 | viewModel.onDeleteDocument(context, document, password, idToken),
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
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 | 9.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/lib/ui/app/icon_text.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | class IconText extends StatelessWidget {
5 | const IconText({
6 | this.text,
7 | this.icon,
8 | this.style,
9 | this.alignment,
10 | });
11 |
12 | final String text;
13 | final IconData icon;
14 | final TextStyle style;
15 | final MainAxisAlignment alignment;
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return Row(
20 | mainAxisAlignment: alignment ?? MainAxisAlignment.start,
21 | mainAxisSize: MainAxisSize.min,
22 | children: [
23 | Icon(icon, color: style?.color),
24 | SizedBox(width: 10),
25 | Flexible(
26 | child: Text(
27 | text ?? '',
28 | style: style,
29 | overflow: TextOverflow.ellipsis,
30 | ),
31 | ),
32 | ],
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lib/ui/client/view/client_view_documents.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/ui/app/document_grid.dart';
6 | import 'package:invoiceninja_flutter/ui/client/view/client_view_vm.dart';
7 |
8 | class ClientViewDocuments extends StatelessWidget {
9 | const ClientViewDocuments({Key key, @required this.viewModel})
10 | : super(key: key);
11 |
12 | final ClientViewVM viewModel;
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | final client = viewModel.client;
17 |
18 | return DocumentGrid(
19 | documents: client.documents.toList(),
20 | onUploadDocument: (path) => viewModel.onUploadDocument(context, path),
21 | onDeleteDocument: (document, password, idToken) =>
22 | viewModel.onDeleteDocument(context, document, password, idToken),
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/lib/ui/product/view/product_view_documents.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/ui/app/document_grid.dart';
6 | import 'package:invoiceninja_flutter/ui/app/screen_imports.dart';
7 |
8 | class ProductViewDocuments extends StatelessWidget {
9 | const ProductViewDocuments({Key key, @required this.viewModel})
10 | : super(key: key);
11 |
12 | final ProductViewVM viewModel;
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | final product = viewModel.product;
17 |
18 | return DocumentGrid(
19 | documents: product.documents.toList(),
20 | onUploadDocument: (path) => viewModel.onUploadDocument(context, path),
21 | onDeleteDocument: (document, password, idToken) =>
22 | viewModel.onDeleteDocument(context, document, password, idToken),
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/lib/ui/vendor/view/vendor_view_documents.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/ui/app/document_grid.dart';
6 | import 'package:invoiceninja_flutter/ui/vendor/view/vendor_view_vm.dart';
7 |
8 | class VendorViewDocuments extends StatelessWidget {
9 | const VendorViewDocuments({Key key, @required this.viewModel})
10 | : super(key: key);
11 |
12 | final VendorViewVM viewModel;
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | final vendor = viewModel.vendor;
17 |
18 | return DocumentGrid(
19 | documents: vendor.documents.toList(),
20 | onUploadDocument: (path) => viewModel.onUploadDocument(context, path),
21 | onDeleteDocument: (document, password, idToken) =>
22 | viewModel.onDeleteDocument(context, document, password, idToken),
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/linux/flutter/generated_plugins.cmake:
--------------------------------------------------------------------------------
1 | #
2 | # Generated file, do not edit.
3 | #
4 |
5 | list(APPEND FLUTTER_PLUGIN_LIST
6 | printing
7 | sentry_flutter
8 | url_launcher_linux
9 | )
10 |
11 | list(APPEND FLUTTER_FFI_PLUGIN_LIST
12 | )
13 |
14 | set(PLUGIN_BUNDLED_LIBRARIES)
15 |
16 | foreach(plugin ${FLUTTER_PLUGIN_LIST})
17 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
18 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
21 | endforeach(plugin)
22 |
23 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
24 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
25 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
26 | endforeach(ffi_plugin)
27 |
--------------------------------------------------------------------------------
/lib/ui/project/view/project_view_documents.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/ui/app/document_grid.dart';
6 | import 'package:invoiceninja_flutter/ui/project/view/project_view_vm.dart';
7 |
8 | class ProjectViewDocuments extends StatelessWidget {
9 | const ProjectViewDocuments({Key key, @required this.viewModel})
10 | : super(key: key);
11 |
12 | final ProjectViewVM viewModel;
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | final project = viewModel.project;
17 |
18 | return DocumentGrid(
19 | documents: project.documents.toList(),
20 | onUploadDocument: (path) => viewModel.onUploadDocument(context, path),
21 | onDeleteDocument: (document, password, idToken) =>
22 | viewModel.onDeleteDocument(context, document, password, idToken),
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/stubs/ui/stub/stub_presenter:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:invoiceninja_flutter/data/models/models.dart';
3 | import 'package:invoiceninja_flutter/ui/app/presenters/entity_presenter.dart';
4 |
5 | class StubPresenter extends EntityPresenter {
6 |
7 | static List getDefaultTableFields(UserCompanyEntity userCompany) {
8 | return [
9 |
10 | ];
11 | }
12 |
13 | static List getAllTableFields(UserCompanyEntity userCompany) {
14 | return [
15 | ...getDefaultTableFields(userCompany),
16 | ...EntityPresenter.getBaseFields(),
17 | ];
18 | }
19 |
20 | @override
21 | Widget getField({String field, BuildContext context}) {
22 | //final state = StoreProvider.of(context).state;
23 | //final stub = entity as InvoiceEntity;
24 |
25 | switch (field) {
26 | }
27 |
28 | return super.getField(field: field, context: context);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/windows/flutter/generated_plugins.cmake:
--------------------------------------------------------------------------------
1 | #
2 | # Generated file, do not edit.
3 | #
4 |
5 | list(APPEND FLUTTER_PLUGIN_LIST
6 | printing
7 | sentry_flutter
8 | url_launcher_windows
9 | )
10 |
11 | list(APPEND FLUTTER_FFI_PLUGIN_LIST
12 | )
13 |
14 | set(PLUGIN_BUNDLED_LIBRARIES)
15 |
16 | foreach(plugin ${FLUTTER_PLUGIN_LIST})
17 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
18 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
21 | endforeach(plugin)
22 |
23 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
24 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
25 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
26 | endforeach(ffi_plugin)
27 |
--------------------------------------------------------------------------------
/lib/ui/design/design_presenter.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/data/models/models.dart';
6 | import 'package:invoiceninja_flutter/ui/app/presenters/entity_presenter.dart';
7 |
8 | class DesignPresenter extends EntityPresenter {
9 | static List getDefaultTableFields(UserCompanyEntity userCompany) {
10 | return [];
11 | }
12 |
13 | static List getAllTableFields(UserCompanyEntity userCompany) {
14 | return [
15 | ...getDefaultTableFields(userCompany),
16 | ];
17 | }
18 |
19 | @override
20 | Widget getField({String field, BuildContext context}) {
21 | //final state = StoreProvider.of(context).state;
22 | //final design = entity as InvoiceEntity;
23 |
24 | switch (field) {
25 | //
26 | }
27 |
28 | return super.getField(field: field, context: context);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lib/ui/payment_term/payment_term_presenter.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/data/models/models.dart';
6 | import 'package:invoiceninja_flutter/ui/app/presenters/entity_presenter.dart';
7 |
8 | class PaymentTermPresenter extends EntityPresenter {
9 | static List getDefaultTableFields(UserCompanyEntity userCompany) {
10 | return [];
11 | }
12 |
13 | static List getAllTableFields(UserCompanyEntity userCompany) {
14 | return [
15 | ...getDefaultTableFields(userCompany),
16 | ];
17 | }
18 |
19 | @override
20 | Widget getField({String field, BuildContext context}) {
21 | //final state = StoreProvider.of(context).state;
22 | //final paymentTerm = entity as InvoiceEntity;
23 |
24 | switch (field) {
25 | }
26 |
27 | return super.getField(field: field, context: context);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/lib/ui/purchase_order/purchase_order_presenter.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:invoiceninja_flutter/data/models/models.dart';
3 | import 'package:invoiceninja_flutter/ui/app/presenters/entity_presenter.dart';
4 |
5 | class PurchaseOrderPresenter extends EntityPresenter {
6 | static List getDefaultTableFields(UserCompanyEntity userCompany) {
7 | return [];
8 | }
9 |
10 | static List getAllTableFields(UserCompanyEntity userCompany) {
11 | return [
12 | ...getDefaultTableFields(userCompany),
13 | ...EntityPresenter.getBaseFields(),
14 | ];
15 | }
16 |
17 | @override
18 | Widget getField({String field, BuildContext context}) {
19 | //final state = StoreProvider.of(context).state;
20 | //final purchaseOrder = entity as InvoiceEntity;
21 |
22 | switch (field) {
23 | }
24 |
25 | return super.getField(field: field, context: context);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/ui/token/token_presenter.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/data/models/models.dart';
6 | import 'package:invoiceninja_flutter/ui/app/presenters/entity_presenter.dart';
7 |
8 | class TokenPresenter extends EntityPresenter {
9 | static List getDefaultTableFields(UserCompanyEntity userCompany) {
10 | return [];
11 | }
12 |
13 | static List getAllTableFields(UserCompanyEntity userCompany) {
14 | return [
15 | ...getDefaultTableFields(userCompany),
16 | ...EntityPresenter.getBaseFields(),
17 | ];
18 | }
19 |
20 | @override
21 | Widget getField({String field, BuildContext context}) {
22 | //final state = StoreProvider.of(context).state;
23 | //final token = entity as InvoiceEntity;
24 |
25 | switch (field) {
26 | }
27 |
28 | return super.getField(field: field, context: context);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lib/ui/webhook/webhook_presenter.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/data/models/models.dart';
6 | import 'package:invoiceninja_flutter/ui/app/presenters/entity_presenter.dart';
7 |
8 | class WebhookPresenter extends EntityPresenter {
9 | static List getDefaultTableFields(UserCompanyEntity userCompany) {
10 | return [];
11 | }
12 |
13 | static List getAllTableFields(UserCompanyEntity userCompany) {
14 | return [
15 | ...getDefaultTableFields(userCompany),
16 | ...EntityPresenter.getBaseFields(),
17 | ];
18 | }
19 |
20 | @override
21 | Widget getField({String field, BuildContext context}) {
22 | //final state = StoreProvider.of(context).state;
23 | //final webhook = entity as InvoiceEntity;
24 |
25 | switch (field) {
26 | }
27 |
28 | return super.getField(field: field, context: context);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lib/ui/task_status/task_status_presenter.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/data/models/models.dart';
6 | import 'package:invoiceninja_flutter/ui/app/presenters/entity_presenter.dart';
7 |
8 | class TaskStatusPresenter extends EntityPresenter {
9 | static List getDefaultTableFields(UserCompanyEntity userCompany) {
10 | return [];
11 | }
12 |
13 | static List getAllTableFields(UserCompanyEntity userCompany) {
14 | return [
15 | ...getDefaultTableFields(userCompany),
16 | ...EntityPresenter.getBaseFields(),
17 | ];
18 | }
19 |
20 | @override
21 | Widget getField({String field, BuildContext context}) {
22 | //final state = StoreProvider.of(context).state;
23 | //final taskStatus = entity as InvoiceEntity;
24 |
25 | switch (field) {
26 | }
27 |
28 | return super.getField(field: field, context: context);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lib/ui/subscription/subscription_presenter.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/data/models/models.dart';
6 | import 'package:invoiceninja_flutter/ui/app/presenters/entity_presenter.dart';
7 |
8 | class SubscriptionPresenter extends EntityPresenter {
9 | static List getDefaultTableFields(UserCompanyEntity userCompany) {
10 | return [];
11 | }
12 |
13 | static List getAllTableFields(UserCompanyEntity userCompany) {
14 | return [
15 | ...getDefaultTableFields(userCompany),
16 | ...EntityPresenter.getBaseFields(),
17 | ];
18 | }
19 |
20 | @override
21 | Widget getField({String field, BuildContext context}) {
22 | //final state = StoreProvider.of(context).state;
23 | //final subscription = entity as InvoiceEntity;
24 |
25 | switch (field) {
26 | }
27 |
28 | return super.getField(field: field, context: context);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lib/utils/money.dart:
--------------------------------------------------------------------------------
1 | // Package imports:
2 | import 'package:built_collection/built_collection.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/constants.dart';
6 | import 'package:invoiceninja_flutter/data/models/static/currency_model.dart';
7 |
8 | double getExchangeRate(BuiltMap currencyMap,
9 | {String fromCurrencyId, String toCurrencyId}) {
10 | if ((fromCurrencyId ?? '').isEmpty || (toCurrencyId ?? '').isEmpty) {
11 | return 1;
12 | }
13 | final fromCurrency = currencyMap[fromCurrencyId];
14 | final toCurrency = currencyMap[toCurrencyId];
15 | // TODO replace with data from server
16 | final baseCurrency = currencyMap[kCurrencyUSDollar];
17 |
18 | if (fromCurrency == baseCurrency) {
19 | return toCurrency.exchangeRate;
20 | }
21 |
22 | if (toCurrency == baseCurrency) {
23 | return 1 / (fromCurrency?.exchangeRate ?? 1);
24 | }
25 |
26 | return toCurrency.exchangeRate * (1 / fromCurrency.exchangeRate);
27 | }
28 |
--------------------------------------------------------------------------------
/lib/ui/app/loading_indicator.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | class LoadingIndicator extends StatelessWidget {
5 | const LoadingIndicator({Key key, this.useCard = false, this.height})
6 | : super(key: key);
7 |
8 | final double height;
9 | final bool useCard;
10 |
11 | @override
12 | Widget build(BuildContext context) {
13 | if (useCard) {
14 | return Padding(
15 | padding: EdgeInsets.all(16),
16 | child: SizedBox(
17 | height: 200.0,
18 | width: double.infinity,
19 | child: Card(
20 | elevation: 4.0,
21 | child: Center(
22 | child: CircularProgressIndicator(),
23 | ),
24 | ),
25 | ),
26 | );
27 | }
28 |
29 | return Container(
30 | height: height ?? double.infinity,
31 | width: double.infinity,
32 | child: Center(
33 | child: CircularProgressIndicator(),
34 | ),
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lib/ui/expense/view/expense_view_documents.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/data/models/models.dart';
6 | import 'package:invoiceninja_flutter/ui/app/document_grid.dart';
7 | import 'package:invoiceninja_flutter/ui/expense/view/expense_view_vm.dart';
8 |
9 | class ExpenseViewDocuments extends StatelessWidget {
10 | const ExpenseViewDocuments(
11 | {@required this.expense, @required this.viewModel});
12 |
13 | final AbstractExpenseViewVM viewModel;
14 | final ExpenseEntity expense;
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | return DocumentGrid(
19 | documents: expense.documents.toList(),
20 | onUploadDocument: (path) => viewModel.onUploadDocument(context, path),
21 | onDeleteDocument: (document, password, idToken) =>
22 | viewModel.onDeleteDocument(context, document, password, idToken),
23 | onViewExpense: null,
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lib/ui/expense_category/expense_category_presenter.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/data/models/models.dart';
6 | import 'package:invoiceninja_flutter/ui/app/presenters/entity_presenter.dart';
7 |
8 | class ExpenseCategoryPresenter extends EntityPresenter {
9 | static List getDefaultTableFields(UserCompanyEntity userCompany) {
10 | return [];
11 | }
12 |
13 | static List getAllTableFields(UserCompanyEntity userCompany) {
14 | return [
15 | ...getDefaultTableFields(userCompany),
16 | ...EntityPresenter.getBaseFields(),
17 | ];
18 | }
19 |
20 | @override
21 | Widget getField({String field, BuildContext context}) {
22 | //final state = StoreProvider.of(context).state;
23 | //final expenseCategory = entity as InvoiceEntity;
24 |
25 | switch (field) {
26 | }
27 |
28 | return super.getField(field: field, context: context);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/lib/ui/app/buttons/edit_icon_button.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Package imports:
5 | import 'package:flutter_redux/flutter_redux.dart';
6 |
7 | // Project imports:
8 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
9 | import 'package:invoiceninja_flutter/utils/localization.dart';
10 |
11 | class EditIconButton extends StatelessWidget {
12 | const EditIconButton({
13 | this.onPressed,
14 | this.isVisible,
15 | });
16 |
17 | final bool isVisible;
18 | final Function onPressed;
19 |
20 | @override
21 | Widget build(BuildContext context) {
22 | final localization = AppLocalization.of(context);
23 | final store = StoreProvider.of(context);
24 |
25 | if (!isVisible) {
26 | return Container();
27 | }
28 |
29 | return TextButton(
30 | child: Text(
31 | localization.edit,
32 | style: TextStyle(color: store.state.headerTextColor),
33 | ),
34 | onPressed: onPressed,
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/.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.
5 |
6 | version:
7 | revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268
8 | channel: stable
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268
17 | base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268
18 | - platform: macos
19 | create_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268
20 | base_revision: fb57da5f945d02ef4f98dfd9409a72b7cce74268
21 |
22 | # User provided section
23 |
24 | # List of Local paths (relative to this file) that should be
25 | # ignored by the migrate tool.
26 | #
27 | # Files that are not part of the templates will be ignored by default.
28 | unmanaged_files:
29 | - 'lib/main.dart'
30 | - 'ios/Runner.xcodeproj/project.pbxproj'
31 |
--------------------------------------------------------------------------------
/lib/data/repositories/static/static_repository.dart:
--------------------------------------------------------------------------------
1 | // Dart imports:
2 | import 'dart:async';
3 | import 'dart:core';
4 |
5 | // Project imports:
6 | import 'package:invoiceninja_flutter/data/models/models.dart';
7 | import 'package:invoiceninja_flutter/data/models/serializers.dart';
8 | import 'package:invoiceninja_flutter/data/models/static/static_data_model.dart';
9 | import 'package:invoiceninja_flutter/data/web_client.dart';
10 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
11 |
12 | class StaticRepository {
13 | const StaticRepository({
14 | this.webClient = const WebClient(),
15 | });
16 |
17 | final WebClient webClient;
18 |
19 | Future loadList(Credentials credentials) async {
20 | final dynamic response =
21 | await webClient.get(credentials.url + '/static', credentials.token);
22 |
23 | final StaticDataItemResponse staticDataResponse = serializers
24 | .deserializeWith(StaticDataItemResponse.serializer, response);
25 |
26 | return staticDataResponse.data;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/lib/ui/app/progress_button.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/ui/app/buttons/elevated_button.dart';
6 |
7 | class ProgressButton extends StatelessWidget {
8 | const ProgressButton({
9 | Key key,
10 | @required this.label,
11 | @required this.isLoading,
12 | @required this.onPressed,
13 | this.padding,
14 | }) : super(key: key);
15 |
16 | final String label;
17 | final bool isLoading;
18 | final Function onPressed;
19 | final EdgeInsetsGeometry padding;
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 | return isLoading
24 | ? Center(
25 | child: SizedBox(
26 | height: 48,
27 | width: 48,
28 | child: CircularProgressIndicator(),
29 | ),
30 | )
31 | : AppButton(
32 | width: double.infinity,
33 | label: label,
34 | onPressed: () => onPressed(),
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/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 | #include
11 | #include
12 |
13 | void fl_register_plugins(FlPluginRegistry* registry) {
14 | g_autoptr(FlPluginRegistrar) printing_registrar =
15 | fl_plugin_registry_get_registrar_for_plugin(registry, "PrintingPlugin");
16 | printing_plugin_register_with_registrar(printing_registrar);
17 | g_autoptr(FlPluginRegistrar) sentry_flutter_registrar =
18 | fl_plugin_registry_get_registrar_for_plugin(registry, "SentryFlutterPlugin");
19 | sentry_flutter_plugin_register_with_registrar(sentry_flutter_registrar);
20 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
21 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
22 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
23 | }
24 |
--------------------------------------------------------------------------------
/lib/ui/app/gateways/token_meta.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/data/models/gateway_token_model.dart';
6 |
7 | class TokenMeta extends StatelessWidget {
8 | const TokenMeta({this.meta});
9 |
10 | final GatewayTokenMetaEntity meta;
11 |
12 | @override
13 | Widget build(BuildContext context) {
14 | var cardDetails = '••••';
15 |
16 | if (meta.last4 != null) {
17 | cardDetails += ' ${meta.last4}';
18 | }
19 |
20 | if (meta.expMonth != null && meta.expYear != null) {
21 | cardDetails += ' ${meta.expMonth}/${meta.expYear}';
22 | }
23 |
24 | return Row(
25 | children: [
26 | Image.asset(
27 | 'assets/images/payment_types/${meta.brand}.png',
28 | height: 16,
29 | ),
30 | SizedBox(width: 8),
31 | Flexible(
32 | child: Text(
33 | cardDetails,
34 | overflow: TextOverflow.ellipsis,
35 | ),
36 | ),
37 | ],
38 | );
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/stubs/ui/stub/view/stub_view:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart';
4 | import 'package:invoiceninja_flutter/ui/stub/view/stub_view_vm.dart';
5 | import 'package:invoiceninja_flutter/ui/app/view_scaffold.dart';
6 |
7 | class StubView extends StatefulWidget {
8 |
9 | const StubView({
10 | Key key,
11 | @required this.viewModel,
12 | @required this.isFilter,
13 | }) : super(key: key);
14 |
15 | final StubViewVM viewModel;
16 | final bool isFilter;
17 |
18 | @override
19 | _StubViewState createState() => new _StubViewState();
20 | }
21 |
22 | class _StubViewState extends State {
23 | @override
24 | Widget build(BuildContext context) {
25 | final viewModel = widget.viewModel;
26 | final stub = viewModel.stub;
27 |
28 | return ViewScaffold(
29 | isFilter: widget.isFilter,
30 | entity: stub,
31 | body: ScrollableListView(
32 | children: [
33 | ],
34 | ),
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/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 "win32_window.h"
10 |
11 | // A window that does nothing but host a Flutter view.
12 | class FlutterWindow : public Win32Window {
13 | public:
14 | // Creates a new FlutterWindow hosting a Flutter view running |project|.
15 | explicit FlutterWindow(const flutter::DartProject& project);
16 | virtual ~FlutterWindow();
17 |
18 | protected:
19 | // Win32Window:
20 | bool OnCreate() override;
21 | void OnDestroy() override;
22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
23 | LPARAM const lparam) noexcept override;
24 |
25 | private:
26 | // The project to run.
27 | flutter::DartProject project_;
28 |
29 | // The Flutter instance hosted by this window.
30 | std::unique_ptr flutter_controller_;
31 | };
32 |
33 | #endif // RUNNER_FLUTTER_WINDOW_H_
34 |
--------------------------------------------------------------------------------
/lib/ui/invoice/view/invoice_view_documents.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/data/models/models.dart';
6 | import 'package:invoiceninja_flutter/ui/app/document_grid.dart';
7 | import 'package:invoiceninja_flutter/ui/invoice/view/invoice_view_vm.dart';
8 |
9 | class InvoiceViewDocuments extends StatelessWidget {
10 | const InvoiceViewDocuments(
11 | {Key key, @required this.invoice, @required this.viewModel})
12 | : super(key: key);
13 |
14 | final AbstractInvoiceViewVM viewModel;
15 | final InvoiceEntity invoice;
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return DocumentGrid(
20 | documents: invoice.documents.toList(),
21 | onUploadDocument: (path) => viewModel.onUploadDocument(context, path),
22 | onDeleteDocument: (document, password, idToken) =>
23 | viewModel.onDeleteDocument(context, document, password, idToken),
24 | onViewExpense: (document) => viewModel.onViewExpense(context, document),
25 | );
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/lib/ui/app/lists/list_divider.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Package imports:
5 | import 'package:flutter_redux/flutter_redux.dart';
6 | import 'package:redux/redux.dart';
7 |
8 | // Project imports:
9 | import 'package:invoiceninja_flutter/constants.dart';
10 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
11 | import 'package:invoiceninja_flutter/utils/colors.dart';
12 |
13 | class ListDivider extends StatelessWidget {
14 | @override
15 | Widget build(BuildContext context) {
16 | final Store store = StoreProvider.of(context);
17 | final state = store.state;
18 | final enableDarkMode = state.prefState.enableDarkMode;
19 | final color = convertHexStringToColor(
20 | enableDarkMode ? kDefaultDarkBorderColor : kDefaultLightBorderColor);
21 |
22 | // https://github.com/flutter/flutter/issues/46339#issuecomment-562859241
23 | return Container(
24 | color: color,
25 | child: Divider(
26 | color: color,
27 | thickness: 1.5,
28 | height: 1.5,
29 | ),
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lib/ui/document/view/document_view.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/ui/app/form_card.dart';
6 | import 'package:invoiceninja_flutter/ui/app/view_scaffold.dart';
7 | import 'package:invoiceninja_flutter/ui/document/view/document_view_vm.dart';
8 |
9 | class DocumentView extends StatefulWidget {
10 | const DocumentView({
11 | Key key,
12 | @required this.viewModel,
13 | @required this.isFilter,
14 | }) : super(key: key);
15 |
16 | final DocumentViewVM viewModel;
17 | final bool isFilter;
18 |
19 | @override
20 | _DocumentViewState createState() => new _DocumentViewState();
21 | }
22 |
23 | class _DocumentViewState extends State {
24 | @override
25 | Widget build(BuildContext context) {
26 | final viewModel = widget.viewModel;
27 | final document = viewModel.document;
28 |
29 | return ViewScaffold(
30 | isFilter: widget.isFilter,
31 | entity: document,
32 | body: FormCard(children: [
33 | // STARTER: widgets - do not remove comment
34 | ]),
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lib/ui/purchase_order/view/purchase_order_view.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart';
3 | import 'package:invoiceninja_flutter/ui/purchase_order/view/purchase_order_view_vm.dart';
4 | import 'package:invoiceninja_flutter/ui/app/view_scaffold.dart';
5 |
6 | class PurchaseOrderView extends StatefulWidget {
7 | const PurchaseOrderView({
8 | Key key,
9 | @required this.viewModel,
10 | @required this.isFilter,
11 | }) : super(key: key);
12 |
13 | final PurchaseOrderViewVM viewModel;
14 | final bool isFilter;
15 |
16 | @override
17 | _PurchaseOrderViewState createState() => new _PurchaseOrderViewState();
18 | }
19 |
20 | class _PurchaseOrderViewState extends State {
21 | @override
22 | Widget build(BuildContext context) {
23 | final viewModel = widget.viewModel;
24 | final purchaseOrder = viewModel.purchaseOrder;
25 |
26 | return ViewScaffold(
27 | isFilter: widget.isFilter,
28 | entity: purchaseOrder,
29 | body: ScrollableListView(
30 | children: [],
31 | ),
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/lib/redux/reports/reports_actions.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Package imports:
5 | import 'package:built_collection/built_collection.dart';
6 |
7 | // Project imports:
8 | import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
9 |
10 | class ViewReports implements PersistUI {
11 | ViewReports({
12 | this.report,
13 | this.force = false,
14 | });
15 |
16 | final bool force;
17 | final String report;
18 | }
19 |
20 | class UpdateReportSettings implements PersistUI {
21 | UpdateReportSettings({
22 | @required this.report,
23 | this.filters,
24 | this.chart,
25 | this.group,
26 | this.selectedGroup,
27 | this.subgroup,
28 | this.sortColumn,
29 | this.sortTotalsIndex,
30 | this.customStartDate,
31 | this.customEndDate,
32 | });
33 |
34 | final String report;
35 | final BuiltMap filters;
36 | final String group;
37 | final String selectedGroup;
38 | final String chart;
39 | final String subgroup;
40 | final String sortColumn;
41 | final int sortTotalsIndex;
42 | final String customStartDate;
43 | final String customEndDate;
44 | }
45 |
--------------------------------------------------------------------------------
/macos/Flutter/GeneratedPluginRegistrant.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | import FlutterMacOS
6 | import Foundation
7 |
8 | import package_info
9 | import package_info_plus_macos
10 | import path_provider_macos
11 | import printing
12 | import sentry_flutter
13 | import shared_preferences_macos
14 | import sqflite
15 | import url_launcher_macos
16 |
17 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
18 | FLTPackageInfoPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlugin"))
19 | FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
20 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
21 | PrintingPlugin.register(with: registry.registrar(forPlugin: "PrintingPlugin"))
22 | SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin"))
23 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
24 | SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
25 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
26 | }
27 |
--------------------------------------------------------------------------------
/lib/ui/settings/settings_screen_vm.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter/widgets.dart';
4 |
5 | // Package imports:
6 | import 'package:flutter_redux/flutter_redux.dart';
7 | import 'package:redux/redux.dart';
8 |
9 | // Project imports:
10 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
11 | import 'package:invoiceninja_flutter/ui/settings/settings_screen.dart';
12 | import 'settings_screen.dart';
13 |
14 | class SettingsScreenBuilder extends StatelessWidget {
15 | const SettingsScreenBuilder({Key key}) : super(key: key);
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return StoreConnector(
20 | converter: SettingsScreenVM.fromStore,
21 | builder: (context, vm) {
22 | return SettingsScreen(
23 | viewModel: vm,
24 | );
25 | },
26 | );
27 | }
28 | }
29 |
30 | class SettingsScreenVM {
31 | SettingsScreenVM({@required this.state});
32 |
33 | final AppState state;
34 |
35 | static SettingsScreenVM fromStore(Store store) {
36 | final state = store.state;
37 |
38 | return SettingsScreenVM(
39 | state: state,
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lib/ui/app/forms/user_picker.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Package imports:
5 | import 'package:flutter_redux/flutter_redux.dart';
6 |
7 | // Project imports:
8 | import 'package:invoiceninja_flutter/data/models/entities.dart';
9 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
10 | import 'package:invoiceninja_flutter/redux/user/user_selectors.dart';
11 | import 'package:invoiceninja_flutter/ui/app/forms/dynamic_selector.dart';
12 |
13 | class UserPicker extends StatelessWidget {
14 | const UserPicker({this.userId, this.onChanged});
15 |
16 | final String userId;
17 | final Function(String) onChanged;
18 |
19 | @override
20 | Widget build(BuildContext context) {
21 | final state = StoreProvider.of(context).state;
22 | final userIds = memoizedUserList(state.userState.map);
23 |
24 | if (!state.userCompany.isAdmin) {
25 | return SizedBox();
26 | }
27 |
28 | if (state.isHosted && !state.isEnterprisePlan) {
29 | return SizedBox();
30 | }
31 |
32 | return DynamicSelector(
33 | onChanged: onChanged,
34 | entityType: EntityType.user,
35 | entityId: userId,
36 | entityIds: userIds,
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/ui/app/live_text.dart:
--------------------------------------------------------------------------------
1 | // Dart imports:
2 | import 'dart:async';
3 |
4 | // Flutter imports:
5 | import 'package:flutter/widgets.dart';
6 |
7 | class LiveText extends StatefulWidget {
8 | const LiveText(this.value, {this.style, this.duration});
9 |
10 | final Duration duration;
11 | final Function value;
12 | final TextStyle style;
13 |
14 | @override
15 | _LiveTextState createState() => _LiveTextState();
16 | }
17 |
18 | class _LiveTextState extends State {
19 | Timer _timer;
20 |
21 | @override
22 | void initState() {
23 | super.initState();
24 | _timer = Timer.periodic(
25 | widget.duration ?? Duration(milliseconds: 100),
26 | (Timer timer) => mounted ? setState(() => false) : false,
27 | );
28 | }
29 |
30 | @override
31 | void dispose() {
32 | _timer.cancel();
33 | _timer = null;
34 | super.dispose();
35 | }
36 |
37 | @override
38 | Widget build(BuildContext context) {
39 | final String value = widget.value() ?? '';
40 |
41 | if (value.isEmpty) {
42 | return SizedBox();
43 | }
44 |
45 | return Text(
46 | value,
47 | style: widget.style,
48 | maxLines: 1,
49 | overflow: TextOverflow.ellipsis,
50 | );
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/lib/ui/auth/init_screen.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Package imports:
5 | import 'package:flutter_redux/flutter_redux.dart';
6 | import 'package:redux/redux.dart';
7 |
8 | // Project imports:
9 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
10 | import 'package:invoiceninja_flutter/redux/auth/auth_actions.dart';
11 |
12 | class InitScreen extends StatelessWidget {
13 | @override
14 | Widget build(BuildContext context) {
15 | return StoreBuilder(
16 | onInit: (Store store) =>
17 | store.dispatch(LoadStateRequest(context)),
18 | builder: (BuildContext context, Store store) {
19 | return Container(
20 | color: Colors.white,
21 | child: Column(
22 | mainAxisSize: MainAxisSize.max,
23 | children: [
24 | Expanded(
25 | child: Center(child: Image.asset('assets/images/icon.png')),
26 | ),
27 | SizedBox(
28 | height: 4.0,
29 | child: LinearProgressIndicator(),
30 | )
31 | ],
32 | ),
33 | );
34 | });
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lib/ui/dashboard/dashboard_activity.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/ui/app/lists/activity_list_tile.dart';
6 | import 'package:invoiceninja_flutter/ui/app/lists/list_divider.dart';
7 | import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart';
8 | import 'package:invoiceninja_flutter/ui/dashboard/dashboard_screen_vm.dart';
9 |
10 | class DashboardActivity extends StatelessWidget {
11 | const DashboardActivity({
12 | Key key,
13 | @required this.viewModel,
14 | }) : super(key: key);
15 |
16 | final DashboardVM viewModel;
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | final company = viewModel.state.company;
21 | final activities = company.activities;
22 |
23 | return Material(
24 | color: Theme.of(context).backgroundColor,
25 | child: ScrollableListViewBuilder(
26 | itemCount: activities.length,
27 | separatorBuilder: (context, index) => ListDivider(),
28 | itemBuilder: (BuildContext context, index) {
29 | final activity = activities[index];
30 | return ActivityListTile(activity: activity);
31 | },
32 | ),
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/lib/ui/client/view/client_view_system_logs.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/cupertino.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | // Project imports:
6 | import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
7 | import 'package:invoiceninja_flutter/ui/app/system_log_viewer.dart';
8 | import 'package:invoiceninja_flutter/ui/client/view/client_view_vm.dart';
9 |
10 | class ClientViewSystemLogs extends StatefulWidget {
11 | const ClientViewSystemLogs({Key key, this.viewModel}) : super(key: key);
12 |
13 | final ClientViewVM viewModel;
14 |
15 | @override
16 | _ClientViewSystemLogsState createState() => _ClientViewSystemLogsState();
17 | }
18 |
19 | class _ClientViewSystemLogsState extends State {
20 | @override
21 | void didChangeDependencies() {
22 | if (widget.viewModel.client.isStale) {
23 | widget.viewModel.onRefreshed(context);
24 | }
25 | super.didChangeDependencies();
26 | }
27 |
28 | @override
29 | Widget build(BuildContext context) {
30 | final client = widget.viewModel.client;
31 |
32 | if (client.isStale) {
33 | return LoadingIndicator();
34 | }
35 |
36 | return SystemLogViewer(
37 | systemLogs: client.systemLogs,
38 | );
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/ui/settings/import_export_vm.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter/widgets.dart';
4 |
5 | // Package imports:
6 | import 'package:flutter_redux/flutter_redux.dart';
7 | import 'package:redux/redux.dart';
8 |
9 | // Project imports:
10 | import 'package:invoiceninja_flutter/constants.dart';
11 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
12 | import 'package:invoiceninja_flutter/ui/settings/import_export.dart';
13 |
14 | class ImportExportScreen extends StatelessWidget {
15 | const ImportExportScreen({Key key}) : super(key: key);
16 | static const String route = '/$kSettings/$kSettingsImportExport';
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return StoreConnector(
21 | converter: ImportExportVM.fromStore,
22 | builder: (context, viewModel) {
23 | return ImportExport(viewModel: viewModel);
24 | },
25 | );
26 | }
27 | }
28 |
29 | class ImportExportVM {
30 | ImportExportVM({
31 | @required this.state,
32 | });
33 |
34 | static ImportExportVM fromStore(Store store) {
35 | final state = store.state;
36 |
37 | return ImportExportVM(
38 | state: state,
39 | );
40 | }
41 |
42 | final AppState state;
43 | }
44 |
--------------------------------------------------------------------------------
/lib/redux/dashboard/dashboard_actions.dart:
--------------------------------------------------------------------------------
1 | // Project imports:
2 | import 'package:invoiceninja_flutter/data/models/dashboard_model.dart';
3 | import 'package:invoiceninja_flutter/data/models/entities.dart';
4 | import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
5 |
6 | class ViewDashboard implements PersistUI {
7 | ViewDashboard({
8 | this.force = false,
9 | this.filter,
10 | });
11 |
12 | final bool force;
13 | final String filter;
14 | }
15 |
16 | class UpdateDashboardSettings implements PersistUI {
17 | UpdateDashboardSettings({
18 | this.settings,
19 | this.offset,
20 | this.currencyId,
21 | this.includeTaxes,
22 | });
23 |
24 | DashboardSettings settings;
25 | int offset;
26 | String currencyId;
27 | bool includeTaxes;
28 | }
29 |
30 | class UpdateDashboardSelection implements PersistUI {
31 | UpdateDashboardSelection({
32 | this.entityType,
33 | this.entityIds,
34 | });
35 |
36 | EntityType entityType;
37 | List entityIds;
38 | }
39 |
40 | class UpdateDashboardEntityType implements PersistUI {
41 | UpdateDashboardEntityType({this.entityType});
42 |
43 | EntityType entityType;
44 | }
45 |
46 | class UpdateDashboardSidebar implements PersistUI {
47 | UpdateDashboardSidebar({this.showSidebar});
48 |
49 | bool showSidebar;
50 | }
51 |
--------------------------------------------------------------------------------
/windows/runner/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.14)
2 | project(runner LANGUAGES CXX)
3 |
4 | # Define the application target. To change its name, change BINARY_NAME in the
5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
6 | # work.
7 | #
8 | # Any new source files that you add to the application should be added here.
9 | add_executable(${BINARY_NAME} WIN32
10 | "flutter_window.cpp"
11 | "main.cpp"
12 | "utils.cpp"
13 | "win32_window.cpp"
14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
15 | "Runner.rc"
16 | "runner.exe.manifest"
17 | )
18 |
19 | # Apply the standard set of build settings. This can be removed for applications
20 | # that need different build settings.
21 | apply_standard_settings(${BINARY_NAME})
22 |
23 | # Disable Windows macros that collide with C++ standard library functions.
24 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
25 |
26 | # Add dependency libraries and include directories. Add any application-specific
27 | # dependencies here.
28 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
29 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
30 |
31 | # Run the Flutter tool portions of the build. This must not be removed.
32 | add_dependencies(${BINARY_NAME} flutter_assemble)
33 |
--------------------------------------------------------------------------------
/lib/ui/app/icon_message.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | class IconMessage extends StatelessWidget {
5 | const IconMessage(
6 | this.text, {
7 | this.iconData,
8 | this.color,
9 | this.trailing,
10 | });
11 |
12 | final String text;
13 | final IconData iconData;
14 | final Color color;
15 | final Widget trailing;
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return Container(
20 | color: color ?? Theme.of(context).primaryColorDark,
21 | child: Padding(
22 | padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 20),
23 | child: Row(
24 | children: [
25 | Icon(
26 | iconData ?? Icons.info_outline,
27 | size: 18.0,
28 | color: Colors.white,
29 | ),
30 | SizedBox(width: 16),
31 | Expanded(
32 | child: Text(
33 | text,
34 | maxLines: null,
35 | style: TextStyle(
36 | color: Colors.white,
37 | ),
38 | ),
39 | ),
40 | if (trailing != null) ...[
41 | SizedBox(width: 16),
42 | trailing,
43 | ]
44 | ],
45 | ),
46 | ),
47 | );
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/lib/ui/app/lists/selected_indicator.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Package imports:
5 | import 'package:flutter_redux/flutter_redux.dart';
6 | import 'package:redux/redux.dart';
7 |
8 | // Project imports:
9 | import 'package:invoiceninja_flutter/constants.dart';
10 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
11 | import 'package:invoiceninja_flutter/utils/colors.dart';
12 |
13 | class SelectedIndicator extends StatelessWidget {
14 | const SelectedIndicator({this.child, this.isSelected, this.isMenu = false});
15 |
16 | final Widget child;
17 | final bool isSelected;
18 | final bool isMenu;
19 |
20 | @override
21 | Widget build(BuildContext context) {
22 | final Store store = StoreProvider.of(context);
23 | final state = store.state;
24 | final enableDarkMode = state.prefState.enableDarkMode;
25 |
26 | return Material(
27 | color: isSelected
28 | ? convertHexStringToColor(enableDarkMode
29 | ? (isMenu
30 | ? kDefaultDarkSelectedColorMenu
31 | : kDefaultDarkSelectedColor)
32 | : (isMenu
33 | ? kDefaultLightSelectedColorMenu
34 | : kDefaultLightSelectedColor))
35 | : Theme.of(context).cardColor,
36 | child: child,
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/lib/redux/reports/reports_state.dart:
--------------------------------------------------------------------------------
1 | // Package imports:
2 | import 'package:built_collection/built_collection.dart';
3 | import 'package:built_value/built_value.dart';
4 | import 'package:built_value/serializer.dart';
5 |
6 | // Project imports:
7 | import 'package:invoiceninja_flutter/constants.dart';
8 |
9 | part 'reports_state.g.dart';
10 |
11 | abstract class ReportsUIState
12 | implements Built {
13 | factory ReportsUIState() {
14 | return _$ReportsUIState._(
15 | report: kReportClient,
16 | customStartDate: '',
17 | customEndDate: '',
18 | group: '',
19 | selectedGroup: '',
20 | chart: '',
21 | subgroup: kReportGroupDay,
22 | filters: BuiltMap(),
23 | );
24 | }
25 |
26 | ReportsUIState._();
27 |
28 | @override
29 | @memoized
30 | int get hashCode;
31 |
32 | String get report;
33 |
34 | String get group;
35 |
36 | String get selectedGroup;
37 |
38 | String get chart;
39 |
40 | String get subgroup;
41 |
42 | String get customStartDate;
43 |
44 | String get customEndDate;
45 |
46 | BuiltMap get filters;
47 |
48 | bool get isGroupByFiltered =>
49 | filters.containsKey(group) && filters[group].isNotEmpty;
50 |
51 | static Serializer get serializer =>
52 | _$reportsUIStateSerializer;
53 | }
54 |
--------------------------------------------------------------------------------
/lib/ui/app/forms/app_tab_bar.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Package imports:
5 | import 'package:flutter_redux/flutter_redux.dart';
6 |
7 | // Project imports:
8 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
9 |
10 | class AppTabBar extends StatelessWidget {
11 | const AppTabBar({
12 | this.tabs,
13 | this.controller,
14 | this.isScrollable = false,
15 | this.onTap,
16 | });
17 |
18 | final List tabs;
19 | final TabController controller;
20 | final bool isScrollable;
21 | final Function(int) onTap;
22 |
23 | @override
24 | Widget build(BuildContext context) {
25 | final store = StoreProvider.of(context);
26 | final state = store.state;
27 |
28 | final tabBar = TabBar(
29 | tabs: tabs,
30 | controller: controller,
31 | isScrollable: isScrollable,
32 | indicatorColor: Theme.of(context).colorScheme.secondary,
33 | onTap: onTap,
34 | );
35 |
36 | if (state.prefState.enableDarkMode || !state.hasAccentColor) {
37 | return tabBar;
38 | }
39 |
40 | return Theme(
41 | data: ThemeData(
42 | tabBarTheme: TabBarTheme(
43 | labelColor: Colors.black,
44 | unselectedLabelColor: Colors.black.withOpacity(.65),
45 | ),
46 | ),
47 | child: tabBar,
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/lib/ui/app/buttons/app_text_button.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Package imports:
5 | import 'package:flutter_redux/flutter_redux.dart';
6 |
7 | // Project imports:
8 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
9 |
10 | class AppTextButton extends StatelessWidget {
11 | const AppTextButton({
12 | this.label,
13 | this.onPressed,
14 | this.isInHeader = false,
15 | this.color,
16 | });
17 |
18 | final String label;
19 | final Function onPressed;
20 | final bool isInHeader;
21 | final Color color;
22 |
23 | @override
24 | Widget build(BuildContext context) {
25 | final store = StoreProvider.of(context);
26 | final state = store.state;
27 |
28 | Color primaryColor;
29 | if (onPressed == null) {
30 | //
31 | } else if (color != null) {
32 | primaryColor = color;
33 | } else if (isInHeader) {
34 | primaryColor = state.headerTextColor;
35 | } else if (state.prefState.enableDarkMode) {
36 | primaryColor = Colors.white;
37 | } else {
38 | primaryColor = Colors.black87;
39 | }
40 |
41 | final ButtonStyle flatButtonStyle =
42 | TextButton.styleFrom(primary: primaryColor);
43 |
44 | return TextButton(
45 | style: flatButtonStyle,
46 | onPressed: onPressed,
47 | child: Text(label),
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/lib/utils/localization.dart:
--------------------------------------------------------------------------------
1 | // Dart imports:
2 | import 'dart:async';
3 |
4 | // Flutter imports:
5 | import 'package:flutter/foundation.dart' show SynchronousFuture;
6 | import 'package:flutter/material.dart';
7 |
8 | // Project imports:
9 | import 'package:invoiceninja_flutter/constants.dart';
10 | import 'package:invoiceninja_flutter/utils/i18n.dart';
11 |
12 | class AppLocalization extends LocaleCodeAware with LocalizationsProvider {
13 | AppLocalization(this.locale) : super(locale.toString());
14 |
15 | final Locale locale;
16 |
17 | static Locale createLocale(String locale) {
18 | if (!kLanguages.contains(locale)) {
19 | return Locale('en');
20 | }
21 |
22 | final parts = locale.split('_');
23 | return Locale(parts[0], parts.length > 1 ? parts[1] : null);
24 | }
25 |
26 | static AppLocalization of(BuildContext context) {
27 | return Localizations.of(context, AppLocalization);
28 | }
29 | }
30 |
31 | class AppLocalizationsDelegate extends LocalizationsDelegate {
32 | const AppLocalizationsDelegate();
33 |
34 | @override
35 | bool isSupported(Locale locale) => kLanguages.contains(locale.toString());
36 |
37 | @override
38 | Future load(Locale locale) {
39 | return SynchronousFuture(AppLocalization(locale));
40 | }
41 |
42 | @override
43 | bool shouldReload(AppLocalizationsDelegate old) => false;
44 | }
45 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
15 |
21 |
24 |
25 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
15 |
21 |
24 |
25 |
--------------------------------------------------------------------------------
/macos/Podfile:
--------------------------------------------------------------------------------
1 | platform :osx, '10.11'
2 |
3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
5 |
6 | project 'Runner', {
7 | 'Debug' => :debug,
8 | 'Profile' => :release,
9 | 'Release' => :release,
10 | }
11 |
12 | def flutter_root
13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
14 | unless File.exist?(generated_xcode_build_settings_path)
15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
16 | end
17 |
18 | File.foreach(generated_xcode_build_settings_path) do |line|
19 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
20 | return matches[1].strip if matches
21 | end
22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
23 | end
24 |
25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
26 |
27 | flutter_macos_podfile_setup
28 |
29 | target 'Runner' do
30 | use_frameworks!
31 | use_modular_headers!
32 |
33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
34 | end
35 |
36 | post_install do |installer|
37 | installer.pods_project.targets.each do |target|
38 | flutter_additional_macos_build_settings(target)
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/ui/payment_term/view/payment_term_view.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/ui/app/entity_header.dart';
6 | import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart';
7 | import 'package:invoiceninja_flutter/ui/app/view_scaffold.dart';
8 | import 'package:invoiceninja_flutter/ui/payment_term/view/payment_term_view_vm.dart';
9 | import 'package:invoiceninja_flutter/utils/localization.dart';
10 |
11 | class PaymentTermView extends StatefulWidget {
12 | const PaymentTermView({
13 | Key key,
14 | @required this.viewModel,
15 | }) : super(key: key);
16 |
17 | final PaymentTermViewVM viewModel;
18 |
19 | @override
20 | _PaymentTermViewState createState() => new _PaymentTermViewState();
21 | }
22 |
23 | class _PaymentTermViewState extends State {
24 | @override
25 | Widget build(BuildContext context) {
26 | final viewModel = widget.viewModel;
27 | final paymentTerm = viewModel.paymentTerm;
28 | final localization = AppLocalization.of(context);
29 |
30 | return ViewScaffold(
31 | entity: paymentTerm,
32 | onBackPressed: () => viewModel.onBackPressed(),
33 | body: ScrollableListView(
34 | children: [
35 | EntityHeader(
36 | entity: paymentTerm,
37 | label: localization.name,
38 | value: paymentTerm.name,
39 | ),
40 | ],
41 | ),
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/lib/ui/app/history_drawer_vm.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/widgets.dart';
3 |
4 | // Package imports:
5 | import 'package:flutter_redux/flutter_redux.dart';
6 | import 'package:redux/redux.dart';
7 |
8 | // Project imports:
9 | import 'package:invoiceninja_flutter/data/models/models.dart';
10 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
11 | import 'package:invoiceninja_flutter/ui/app/history_drawer.dart';
12 |
13 | class HistoryDrawerBuilder extends StatelessWidget {
14 | const HistoryDrawerBuilder({Key key}) : super(key: key);
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | return StoreConnector(
19 | converter: AppDrawerVM.fromStore,
20 | builder: (context, viewModel) {
21 | return HistoryDrawer(viewModel: viewModel);
22 | },
23 | );
24 | }
25 | }
26 |
27 | class AppDrawerVM {
28 | AppDrawerVM({
29 | @required this.companies,
30 | @required this.selectedCompany,
31 | @required this.user,
32 | @required this.isLoading,
33 | });
34 |
35 | final List companies;
36 | final CompanyEntity selectedCompany;
37 | final UserEntity user;
38 | final bool isLoading;
39 |
40 | static AppDrawerVM fromStore(Store store) {
41 | final AppState state = store.state;
42 |
43 | return AppDrawerVM(
44 | isLoading: state.isLoading,
45 | companies: state.companies,
46 | user: state.user,
47 | selectedCompany: state.company,
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/lib/data/models/static/font_model.dart:
--------------------------------------------------------------------------------
1 | // Package imports:
2 | import 'package:built_value/built_value.dart';
3 | import 'package:built_value/serializer.dart';
4 |
5 | // Project imports:
6 | import 'package:invoiceninja_flutter/data/models/entities.dart';
7 |
8 | part 'font_model.g.dart';
9 |
10 | class FontFields {
11 | static const String name = 'name';
12 | }
13 |
14 | abstract class FontEntity extends Object
15 | with SelectableEntity
16 | implements Built {
17 | factory FontEntity({String id, String name}) {
18 | return _$FontEntity._(
19 | id: id ?? '',
20 | name: name ?? '',
21 | );
22 | }
23 | FontEntity._();
24 |
25 | @override
26 | @memoized
27 | int get hashCode;
28 |
29 | String get name;
30 |
31 | @override
32 | bool matchesFilter(String filter) {
33 | if (filter == null || filter.isEmpty) {
34 | return true;
35 | }
36 |
37 | filter = filter.toLowerCase();
38 |
39 | if (name.toLowerCase().contains(filter)) {
40 | return true;
41 | }
42 |
43 | return false;
44 | }
45 |
46 | @override
47 | String matchesFilterValue(String filter) {
48 | if (filter == null || filter.isEmpty) {
49 | return null;
50 | }
51 |
52 | filter = filter.toLowerCase();
53 |
54 | return null;
55 | }
56 |
57 | @override
58 | String get listDisplayName {
59 | return name;
60 | }
61 |
62 | @override
63 | double get listDisplayAmount => null;
64 |
65 | static Serializer get serializer => _$fontEntitySerializer;
66 | }
67 |
--------------------------------------------------------------------------------
/lib/ui/app/forms/help_link.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/gestures.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | // Package imports:
6 | import 'package:url_launcher/url_launcher.dart';
7 |
8 | // Project imports:
9 | import 'package:invoiceninja_flutter/utils/localization.dart';
10 |
11 | class HelpLink extends StatelessWidget {
12 | const HelpLink({
13 | @required this.url,
14 | @required this.message,
15 | });
16 |
17 | final String url;
18 | final String message;
19 |
20 | @override
21 | Widget build(BuildContext context) {
22 | final localization = AppLocalization.of(context);
23 | final ThemeData themeData = Theme.of(context);
24 | final TextStyle aboutTextStyle = themeData.textTheme.bodyText2;
25 | final TextStyle linkStyle = themeData.textTheme.bodyText2
26 | .copyWith(color: themeData.colorScheme.secondary);
27 |
28 | return Padding(
29 | padding: const EdgeInsets.only(top: 20, bottom: 30),
30 | child: RichText(
31 | text: TextSpan(
32 | children: [
33 | TextSpan(
34 | style: linkStyle,
35 | recognizer: TapGestureRecognizer()
36 | ..onTap = () {
37 | launch(url, forceSafariVC: false);
38 | },
39 | text: localization.clickHereCapital + ' ',
40 | ),
41 | TextSpan(
42 | style: aboutTextStyle,
43 | text: message,
44 | ),
45 | ],
46 | ),
47 | ),
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/lib/ui/app/forms/app_toggle_buttons.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/redux/ui/pref_state.dart';
6 | import 'package:invoiceninja_flutter/utils/platforms.dart';
7 |
8 | class AppToggleButtons extends StatelessWidget {
9 | const AppToggleButtons({
10 | @required this.selectedIndex,
11 | @required this.onTabChanged,
12 | @required this.tabLabels,
13 | });
14 |
15 | final List tabLabels;
16 | final int selectedIndex;
17 | final Function(int) onTabChanged;
18 |
19 | @override
20 | Widget build(BuildContext context) {
21 | return LayoutBuilder(builder: (context, constraints) {
22 | final bool isDesktop = calculateLayout(context) != AppLayout.mobile;
23 | final double toggleWidth =
24 | isDesktop ? 208 : (constraints.maxWidth - 36) / 2;
25 |
26 | return Padding(
27 | padding: const EdgeInsets.only(bottom: 20),
28 | child: ToggleButtons(
29 | children: [
30 | Container(
31 | width: toggleWidth,
32 | height: 40,
33 | child: Center(child: Text(tabLabels[0])),
34 | ),
35 | Container(
36 | width: toggleWidth,
37 | height: 40,
38 | child: Center(child: Text(tabLabels[1])),
39 | ),
40 | ],
41 | isSelected: selectedIndex == 0 ? [true, false] : [false, true],
42 | onPressed: (index) => onTabChanged(index),
43 | ),
44 | );
45 | });
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/windows/runner/main.cpp:
--------------------------------------------------------------------------------
1 | //#include
2 | //auto bdw = bitsdojo_window_configure(BDW_CUSTOM_FRAME | BDW_HIDE_ON_STARTUP);
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | #include "flutter_window.h"
9 | #include "utils.h"
10 |
11 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
12 | _In_ wchar_t *command_line, _In_ int show_command) {
13 | // Attach to console when present (e.g., 'flutter run') or create a
14 | // new console when running with a debugger.
15 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
16 | CreateAndAttachConsole();
17 | }
18 |
19 | // Initialize COM, so that it is available for use in the library and/or
20 | // plugins.
21 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
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(project);
31 | Win32Window::Point origin(10, 10);
32 | Win32Window::Size size(1280, 720);
33 | if (!window.CreateAndShow(L"Invoice Ninja", origin, size)) {
34 | return EXIT_FAILURE;
35 | }
36 | window.SetQuitOnClose(true);
37 |
38 | ::MSG msg;
39 | while (::GetMessage(&msg, nullptr, 0, 0)) {
40 | ::TranslateMessage(&msg);
41 | ::DispatchMessage(&msg);
42 | }
43 |
44 | ::CoUninitialize();
45 | return EXIT_SUCCESS;
46 | }
47 |
--------------------------------------------------------------------------------
/lib/ui/settings/data_visualizations_vm.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter/widgets.dart';
4 |
5 | // Package imports:
6 | import 'package:flutter_redux/flutter_redux.dart';
7 | import 'package:redux/redux.dart';
8 |
9 | // Project imports:
10 | import 'package:invoiceninja_flutter/constants.dart';
11 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
12 | import 'package:invoiceninja_flutter/ui/settings/data_visualizations.dart';
13 |
14 | class DataVisualizationsScreen extends StatelessWidget {
15 | const DataVisualizationsScreen({Key key}) : super(key: key);
16 | static const String route = '/$kSettings/$kSettingsDataVisualizations';
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return StoreConnector(
21 | converter: DataVisualizationsVM.fromStore,
22 | builder: (context, viewModel) {
23 | return DataVisualizations(viewModel: viewModel);
24 | },
25 | );
26 | }
27 | }
28 |
29 | class DataVisualizationsVM {
30 | DataVisualizationsVM({
31 | @required this.state,
32 | @required this.onSavePressed,
33 | @required this.onCancelPressed,
34 | });
35 |
36 | static DataVisualizationsVM fromStore(Store store) {
37 | final state = store.state;
38 |
39 | return DataVisualizationsVM(
40 | state: state,
41 | onSavePressed: null,
42 | onCancelPressed: null,
43 | );
44 | }
45 |
46 | final AppState state;
47 | final Function(BuildContext) onSavePressed;
48 | final Function(BuildContext) onCancelPressed;
49 | }
50 |
--------------------------------------------------------------------------------
/lib/redux/dashboard/dashboard_middleware.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/widgets.dart';
3 |
4 | // Package imports:
5 | import 'package:redux/redux.dart';
6 |
7 | // Project imports:
8 | import 'package:invoiceninja_flutter/main_app.dart';
9 | import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
10 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
11 | import 'package:invoiceninja_flutter/redux/dashboard/dashboard_actions.dart';
12 | import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
13 | import 'package:invoiceninja_flutter/ui/dashboard/dashboard_screen_vm.dart';
14 |
15 | List> createStoreDashboardMiddleware() {
16 | final viewDashboard = _createViewDashboard();
17 |
18 | return [
19 | TypedMiddleware(viewDashboard),
20 | ];
21 | }
22 |
23 | Middleware _createViewDashboard() {
24 | return (Store store, dynamic dynamicAction, NextDispatcher next) {
25 | final action = dynamicAction as ViewDashboard;
26 |
27 | checkForChanges(
28 | store: store,
29 | force: action.force,
30 | callback: () {
31 | if (store.state.isStale) {
32 | store.dispatch(RefreshData());
33 | }
34 |
35 | store.dispatch(UpdateCurrentRoute(DashboardScreenBuilder.route));
36 |
37 | next(action);
38 |
39 | if (store.state.prefState.isMobile) {
40 | navigatorKey.currentState.pushNamedAndRemoveUntil(
41 | DashboardScreenBuilder.route, (Route route) => false);
42 | }
43 | });
44 | };
45 | }
46 |
--------------------------------------------------------------------------------
/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "16x16",
5 | "idiom" : "mac",
6 | "filename" : "app_icon_16.png",
7 | "scale" : "1x"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "app_icon_32.png",
13 | "scale" : "2x"
14 | },
15 | {
16 | "size" : "32x32",
17 | "idiom" : "mac",
18 | "filename" : "app_icon_32.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "app_icon_64.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "128x128",
29 | "idiom" : "mac",
30 | "filename" : "app_icon_128.png",
31 | "scale" : "1x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "app_icon_256.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "256x256",
41 | "idiom" : "mac",
42 | "filename" : "app_icon_256.png",
43 | "scale" : "1x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "app_icon_512.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "512x512",
53 | "idiom" : "mac",
54 | "filename" : "app_icon_512.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "app_icon_1024.png",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/lib/ui/settings/credit_cards_and_banks_vm.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter/widgets.dart';
4 |
5 | // Package imports:
6 | import 'package:flutter_redux/flutter_redux.dart';
7 | import 'package:redux/redux.dart';
8 |
9 | // Project imports:
10 | import 'package:invoiceninja_flutter/constants.dart';
11 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
12 | import 'package:invoiceninja_flutter/ui/settings/credit_cards_and_banks.dart';
13 |
14 | class CreditCardsAndBanksScreen extends StatelessWidget {
15 | const CreditCardsAndBanksScreen({Key key}) : super(key: key);
16 | static const String route = '/$kSettings/$kSettingsCreditCardsAndBanks';
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return StoreConnector(
21 | converter: CreditCardsAndBanksVM.fromStore,
22 | builder: (context, viewModel) {
23 | return CreditCardsAndBanks(viewModel: viewModel);
24 | },
25 | );
26 | }
27 | }
28 |
29 | class CreditCardsAndBanksVM {
30 | CreditCardsAndBanksVM({
31 | @required this.state,
32 | @required this.onSavePressed,
33 | @required this.onCancelPressed,
34 | });
35 |
36 | static CreditCardsAndBanksVM fromStore(Store store) {
37 | final state = store.state;
38 |
39 | return CreditCardsAndBanksVM(
40 | state: state,
41 | onSavePressed: null,
42 | onCancelPressed: null,
43 | );
44 | }
45 |
46 | final AppState state;
47 | final Function(BuildContext) onSavePressed;
48 | final Function(BuildContext) onCancelPressed;
49 | }
50 |
--------------------------------------------------------------------------------
/lib/redux/reports/reports_reducer.dart:
--------------------------------------------------------------------------------
1 | // Project imports:
2 | import 'package:invoiceninja_flutter/redux/company/company_actions.dart';
3 | import 'package:invoiceninja_flutter/redux/reports/reports_actions.dart';
4 | import 'package:invoiceninja_flutter/redux/reports/reports_state.dart';
5 | import 'package:invoiceninja_flutter/redux/settings/settings_actions.dart';
6 |
7 | ReportsUIState reportsUIReducer(ReportsUIState state, dynamic action) {
8 | if (action is SaveAuthUserSuccess) {
9 | return state.rebuild((b) => b
10 | ..group = ''
11 | ..subgroup = ''
12 | ..selectedGroup = ''
13 | ..chart = '');
14 | } else if (action is UpdateReportSettings) {
15 | if (action.report != null &&
16 | action.report.isNotEmpty &&
17 | action.report != state.report) {
18 | return ReportsUIState().rebuild((b) => b..report = action.report);
19 | } else {
20 | return state.rebuild((b) => b
21 | ..report = action.report ?? state.report
22 | ..group = action.group ?? state.group
23 | ..selectedGroup = action.selectedGroup ?? state.selectedGroup
24 | ..subgroup = action.subgroup ?? state.subgroup
25 | ..chart = action.chart ?? state.chart
26 | ..customStartDate = action.customStartDate ?? state.customStartDate
27 | ..customEndDate = action.customEndDate ?? state.customEndDate
28 | ..filters.replace(action.filters ?? state.filters));
29 | }
30 | } else if (action is SelectCompany) {
31 | //return state.rebuild((b) => b..currencyId = action.company.currencyId);
32 | // TODO re-enable
33 | return state;
34 | }
35 |
36 | return state;
37 | }
38 |
--------------------------------------------------------------------------------
/lib/redux/reports/reports_middleware.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Package imports:
5 | import 'package:redux/redux.dart';
6 |
7 | // Project imports:
8 | import 'package:invoiceninja_flutter/main_app.dart';
9 | import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
10 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
11 | import 'package:invoiceninja_flutter/redux/reports/reports_actions.dart';
12 | import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
13 | import 'package:invoiceninja_flutter/ui/reports/reports_screen.dart';
14 |
15 | List> createStoreReportsMiddleware() {
16 | final viewReports = _viewReports();
17 |
18 | return [
19 | TypedMiddleware(viewReports),
20 | ];
21 | }
22 |
23 | Middleware _viewReports() {
24 | return (Store store, dynamic dynamicAction, NextDispatcher next) {
25 | final action = dynamicAction as ViewReports;
26 |
27 | checkForChanges(
28 | store: store,
29 | force: action.force,
30 | callback: () {
31 | const route = ReportsScreen.route;
32 |
33 | store.dispatch(UpdateCurrentRoute(route));
34 |
35 | next(action);
36 |
37 | if (store.state.prefState.isMobile) {
38 | if (action.report == null) {
39 | navigatorKey.currentState.pushNamedAndRemoveUntil(
40 | ReportsScreen.route, (Route route) => false);
41 | } else {
42 | navigatorKey.currentState.pushNamed(route);
43 | }
44 | }
45 | });
46 | };
47 | }
48 |
--------------------------------------------------------------------------------
/lib/ui/app/resources/cached_image.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/foundation.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | // Package imports:
6 | import 'package:cached_network_image/cached_network_image.dart';
7 | import 'package:flutter_redux/flutter_redux.dart';
8 |
9 | // Project imports:
10 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
11 |
12 | class CachedImage extends StatelessWidget {
13 | const CachedImage(
14 | {this.url, this.width, this.height, this.showNinjaOnError = true});
15 |
16 | final String url;
17 | final bool showNinjaOnError;
18 | final double width;
19 | final double height;
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 | final store = StoreProvider.of(context);
24 | final state = store.state;
25 |
26 | // TODO remove this workaround
27 | if (state.isTesting || (url ?? '').isEmpty) {
28 | return SizedBox(
29 | width: width,
30 | height: height,
31 | );
32 | }
33 |
34 | // TODO remove this
35 | if (kIsWeb) {
36 | return Image.network(
37 | url,
38 | width: width,
39 | height: height,
40 | key: ValueKey(url),
41 | fit: BoxFit.contain,
42 | );
43 | }
44 |
45 | return CachedNetworkImage(
46 | width: width,
47 | height: height,
48 | key: ValueKey(url),
49 | imageUrl: url,
50 | placeholder: (context, url) => Center(child: CircularProgressIndicator()),
51 | errorWidget: (context, url, Object error) =>
52 | Image.asset('assets/images/icon.png', width: 32, height: 30),
53 | );
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/lib/ui/auth/lock_screen.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Package imports:
5 | import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
6 |
7 | // Project imports:
8 | import 'package:invoiceninja_flutter/utils/localization.dart';
9 |
10 | class LockScreen extends StatelessWidget {
11 | const LockScreen({@required this.onAuthenticatePressed});
12 |
13 | final Function onAuthenticatePressed;
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | //final localization = AppLocalization(Locale(Intl.defaultLocale));
18 | final localization = AppLocalization.of(context);
19 |
20 | return Material(
21 | color: Colors.grey,
22 | child: Column(
23 | mainAxisAlignment: MainAxisAlignment.spaceAround,
24 | children: [
25 | Row(
26 | mainAxisAlignment: MainAxisAlignment.center,
27 | children: [
28 | Icon(
29 | MdiIcons.lock,
30 | size: 24.0,
31 | color: Colors.grey[400],
32 | ),
33 | SizedBox(
34 | width: 12.0,
35 | ),
36 | Text(
37 | localization.locked,
38 | style: TextStyle(
39 | fontSize: 32.0,
40 | color: Colors.grey[400],
41 | ),
42 | ),
43 | ],
44 | ),
45 | ElevatedButton(
46 | onPressed: onAuthenticatePressed,
47 | child: Text(localization.authenticate),
48 | )
49 | ],
50 | ),
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/ui/app/responsive_padding.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/constants.dart';
6 | import 'package:invoiceninja_flutter/utils/platforms.dart';
7 |
8 | class ResponsivePadding extends StatelessWidget {
9 | const ResponsivePadding({this.child});
10 |
11 | final Widget child;
12 |
13 | @override
14 | Widget build(BuildContext context) {
15 | /*
16 | final double sidePadding = isMobile(context)
17 | ? kMobileDialogPadding
18 | : (MediaQuery.of(context).size.width - 500) / 2;
19 |
20 | return Padding(
21 | padding: EdgeInsets.only(
22 | left: sidePadding,
23 | top: kMobileDialogPadding,
24 | right: sidePadding,
25 | bottom:
26 | kMobileDialogPadding + MediaQuery.of(context).viewInsets.bottom),
27 | child: child,
28 | );
29 |
30 | */
31 |
32 | if (isMobile(context)) {
33 | return Padding(
34 | padding: EdgeInsets.only(
35 | left: kMobileDialogPadding,
36 | top: kMobileDialogPadding,
37 | right: kMobileDialogPadding,
38 | bottom: kMobileDialogPadding +
39 | MediaQuery.of(context).viewInsets.bottom),
40 | child: child,
41 | );
42 | } else {
43 | return FractionallySizedBox(
44 | child: Padding(
45 | child: child,
46 | padding: EdgeInsets.only(top: kMobileDialogPadding * 2)),
47 | //bottom: (kMobileDialogPadding * 2) +
48 | //MediaQuery.of(context).viewInsets.bottom)),
49 | widthFactor: .4,
50 | );
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/ui/app/forms/growable_form_field.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/ui/app/forms/decorated_form_field.dart';
6 |
7 | class GrowableFormField extends StatefulWidget {
8 | const GrowableFormField({
9 | Key key,
10 | @required this.initialValue,
11 | @required this.onChanged,
12 | this.autofocus = false,
13 | }) : super(key: key);
14 |
15 | final String initialValue;
16 | final ValueChanged onChanged;
17 | final bool autofocus;
18 |
19 | @override
20 | _GrowableFormFieldState createState() => _GrowableFormFieldState();
21 | }
22 |
23 | class _GrowableFormFieldState extends State {
24 | final _focusNode = FocusNode();
25 | bool _hasFocus = false;
26 |
27 | @override
28 | void initState() {
29 | super.initState();
30 | _focusNode.addListener(_onFoucsChanged);
31 | }
32 |
33 | void _onFoucsChanged() {
34 | setState(() {
35 | _hasFocus = _focusNode.hasFocus;
36 | });
37 | }
38 |
39 | @override
40 | void dispose() {
41 | _focusNode.removeListener(_onFoucsChanged);
42 | _focusNode.dispose();
43 |
44 | super.dispose();
45 | }
46 |
47 | @override
48 | Widget build(BuildContext context) {
49 | return DecoratedFormField(
50 | autofocus: widget.autofocus,
51 | focusNode: _focusNode,
52 | initialValue: widget.initialValue,
53 | onChanged: widget.onChanged,
54 | keyboardType: TextInputType.multiline,
55 | minLines: 1,
56 | // TODO remove this isWeb check/needed to prevent overflow
57 | maxLines: _hasFocus ? 20 : 2,
58 | );
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/invoiceninja_android.iml:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/lib/ui/settings/settings_screen.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Package imports:
5 | import 'package:flutter_redux/flutter_redux.dart';
6 |
7 | // Project imports:
8 | import 'package:invoiceninja_flutter/data/models/entities.dart';
9 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
10 | import 'package:invoiceninja_flutter/redux/settings/settings_actions.dart';
11 | import 'package:invoiceninja_flutter/ui/app/list_filter.dart';
12 | import 'package:invoiceninja_flutter/ui/app/list_scaffold.dart';
13 | import 'package:invoiceninja_flutter/ui/settings/settings_list_vm.dart';
14 | import 'package:invoiceninja_flutter/ui/settings/settings_screen_vm.dart';
15 |
16 | class SettingsScreen extends StatelessWidget {
17 | const SettingsScreen({
18 | Key key,
19 | @required this.viewModel,
20 | }) : super(key: key);
21 |
22 | static const String route = '/settings';
23 |
24 | final SettingsScreenVM viewModel;
25 |
26 | @override
27 | Widget build(BuildContext context) {
28 | final store = StoreProvider.of(context);
29 | final state = store.state;
30 |
31 | return ListScaffold(
32 | entityType: EntityType.settings,
33 | appBarTitle: ListFilter(
34 | key:
35 | ValueKey('__cleared_at_${state.settingsUIState.filterClearedAt}__'),
36 | entityType: EntityType.settings,
37 | entityIds: [],
38 | filter: state.settingsUIState.filter,
39 | onFilterChanged: (value) {
40 | store.dispatch(FilterSettings(value));
41 | },
42 | ),
43 | appBarActions: [],
44 | body: SettingsListBuilder(),
45 | );
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/lib/ui/quote/edit/quote_edit_pdf_vm.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Package imports:
5 | import 'package:flutter_redux/flutter_redux.dart';
6 | import 'package:redux/redux.dart';
7 |
8 | // Project imports:
9 | import 'package:invoiceninja_flutter/data/models/models.dart';
10 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
11 | import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_pdf.dart';
12 | import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_pdf_vm.dart';
13 |
14 | class QuoteEditPDFScreen extends StatelessWidget {
15 | const QuoteEditPDFScreen({Key key}) : super(key: key);
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return StoreConnector(
20 | converter: (Store store) {
21 | return QuoteEditPDFVM.fromStore(store);
22 | },
23 | builder: (context, viewModel) {
24 | return InvoiceEditPDF(
25 | viewModel: viewModel,
26 | );
27 | },
28 | );
29 | }
30 | }
31 |
32 | class QuoteEditPDFVM extends EntityEditPDFVM {
33 | QuoteEditPDFVM({
34 | @required CompanyEntity company,
35 | @required InvoiceEntity invoice,
36 | @required AppState state,
37 | }) : super(
38 | company: company,
39 | invoice: invoice,
40 | state: state,
41 | );
42 |
43 | factory QuoteEditPDFVM.fromStore(Store store) {
44 | final AppState state = store.state;
45 | final invoice = state.quoteUIState.editing;
46 |
47 | return QuoteEditPDFVM(
48 | company: state.company,
49 | invoice: invoice,
50 | state: state,
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/ui/credit/edit/credit_edit_pdf_vm.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Package imports:
5 | import 'package:flutter_redux/flutter_redux.dart';
6 | import 'package:redux/redux.dart';
7 |
8 | // Project imports:
9 | import 'package:invoiceninja_flutter/data/models/models.dart';
10 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
11 | import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_pdf.dart';
12 | import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_pdf_vm.dart';
13 |
14 | class CreditEditPDFScreen extends StatelessWidget {
15 | const CreditEditPDFScreen({Key key}) : super(key: key);
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return StoreConnector(
20 | converter: (Store store) {
21 | return CreditEditPDFVM.fromStore(store);
22 | },
23 | builder: (context, viewModel) {
24 | return InvoiceEditPDF(
25 | viewModel: viewModel,
26 | );
27 | },
28 | );
29 | }
30 | }
31 |
32 | class CreditEditPDFVM extends EntityEditPDFVM {
33 | CreditEditPDFVM({
34 | @required CompanyEntity company,
35 | @required InvoiceEntity invoice,
36 | @required AppState state,
37 | }) : super(
38 | company: company,
39 | invoice: invoice,
40 | state: state,
41 | );
42 |
43 | factory CreditEditPDFVM.fromStore(Store store) {
44 | final AppState state = store.state;
45 | final invoice = state.creditUIState.editing;
46 |
47 | return CreditEditPDFVM(
48 | company: state.company,
49 | invoice: invoice,
50 | state: state,
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/ui/client/client_pdf_vm.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Package imports:
5 | import 'package:flutter_redux/flutter_redux.dart';
6 | import 'package:redux/redux.dart';
7 |
8 | // Project imports:
9 | import 'package:invoiceninja_flutter/data/models/models.dart';
10 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
11 | import 'package:invoiceninja_flutter/ui/client/client_pdf.dart';
12 |
13 | class ClientPdfScreen extends StatelessWidget {
14 | const ClientPdfScreen({Key key, this.showAppBar = true}) : super(key: key);
15 |
16 | final bool showAppBar;
17 |
18 | static const String route = '/client/pdf';
19 |
20 | @override
21 | Widget build(BuildContext context) {
22 | return StoreConnector(
23 | converter: (Store store) {
24 | return ClientPdfVM.fromStore(store);
25 | },
26 | builder: (context, vm) {
27 | return ClientPdfView(
28 | key: ValueKey('__client_pdf_${vm.client.id}__'),
29 | viewModel: vm,
30 | showAppBar: showAppBar,
31 | );
32 | },
33 | );
34 | }
35 | }
36 |
37 | class ClientPdfVM {
38 | ClientPdfVM({
39 | @required this.state,
40 | @required this.client,
41 | });
42 |
43 | factory ClientPdfVM.fromStore(Store store) {
44 | final state = store.state;
45 | final clientUIState = state.uiState.clientUIState;
46 | final clientId = clientUIState.selectedId;
47 | final client = state.clientState.get(clientId);
48 |
49 | return ClientPdfVM(
50 | state: state,
51 | client: client,
52 | );
53 | }
54 |
55 | final AppState state;
56 | final ClientEntity client;
57 | }
58 |
--------------------------------------------------------------------------------
/lib/ui/client/view/client_view_activity.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/ui/app/lists/activity_list_tile.dart';
6 | import 'package:invoiceninja_flutter/ui/app/lists/list_divider.dart';
7 | import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
8 | import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart';
9 | import 'package:invoiceninja_flutter/ui/client/view/client_view_vm.dart';
10 |
11 | class ClientViewActivity extends StatefulWidget {
12 | const ClientViewActivity({Key key, this.viewModel}) : super(key: key);
13 |
14 | final ClientViewVM viewModel;
15 |
16 | @override
17 | _ClientViewActivityState createState() => _ClientViewActivityState();
18 | }
19 |
20 | class _ClientViewActivityState extends State {
21 | @override
22 | void didChangeDependencies() {
23 | if (widget.viewModel.client.isStale) {
24 | widget.viewModel.onRefreshed(context);
25 | }
26 | super.didChangeDependencies();
27 | }
28 |
29 | @override
30 | Widget build(BuildContext context) {
31 | final client = widget.viewModel.client;
32 | final activities = client.activities;
33 |
34 | if (!client.isLoaded) {
35 | return LoadingIndicator();
36 | }
37 |
38 | return ScrollableListViewBuilder(
39 | itemCount: activities.length,
40 | padding: const EdgeInsets.symmetric(vertical: 16),
41 | separatorBuilder: (context, index) => ListDivider(),
42 | itemBuilder: (BuildContext context, index) {
43 | final activity = activities[index];
44 | return ActivityListTile(activity: activity);
45 | },
46 | );
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/lib/ui/tax_rate/view/tax_rate_view.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/ui/app/entity_header.dart';
6 | import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart';
7 | import 'package:invoiceninja_flutter/ui/app/view_scaffold.dart';
8 | import 'package:invoiceninja_flutter/ui/tax_rate/view/tax_rate_view_vm.dart';
9 | import 'package:invoiceninja_flutter/utils/formatting.dart';
10 | import 'package:invoiceninja_flutter/utils/localization.dart';
11 |
12 | class TaxRateView extends StatefulWidget {
13 | const TaxRateView({
14 | Key key,
15 | @required this.viewModel,
16 | @required this.isFilter,
17 | }) : super(key: key);
18 |
19 | final TaxRateViewVM viewModel;
20 | final bool isFilter;
21 |
22 | @override
23 | _TaxRateViewState createState() => new _TaxRateViewState();
24 | }
25 |
26 | class _TaxRateViewState extends State {
27 | @override
28 | Widget build(BuildContext context) {
29 | final viewModel = widget.viewModel;
30 | final taxRate = viewModel.taxRate;
31 | final localization = AppLocalization.of(context);
32 |
33 | return ViewScaffold(
34 | isFilter: widget.isFilter,
35 | entity: taxRate,
36 | onBackPressed: () => viewModel.onBackPressed(),
37 | body: ScrollableListView(children: [
38 | EntityHeader(
39 | entity: taxRate,
40 | label: localization.name,
41 | value: taxRate.name,
42 | secondLabel: localization.rate,
43 | secondValue: formatNumber(taxRate.rate, context,
44 | formatNumberType: FormatNumberType.percent),
45 | ),
46 | ]),
47 | );
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/lib/data/models/recurring_invoice_model.dart:
--------------------------------------------------------------------------------
1 | class RecurringInvoiceFields {
2 | static const String amount = 'amount';
3 | static const String clientId = 'client_id';
4 | static const String client = 'client';
5 | static const String statusId = 'status_id';
6 | static const String status = 'status';
7 | static const String discount = 'discount';
8 | static const String number = 'number';
9 | static const String poNumber = 'po_number';
10 | static const String date = 'date';
11 | static const String dueDateDays = 'due_date_days';
12 | static const String terms = 'terms';
13 | static const String footer = 'footer';
14 | static const String publicNotes = 'public_notes';
15 | static const String privateNotes = 'private_notes';
16 | static const String frequencyId = 'frequency_id';
17 | static const String endDate = 'end_date';
18 | static const String documents = 'documents';
19 | static const String customValue1 = 'custom1';
20 | static const String customValue2 = 'custom2';
21 | static const String customValue3 = 'custom3';
22 | static const String customValue4 = 'custom4';
23 | static const String taxAmount = 'tax_amount';
24 | static const String reminder1Sent = 'reminder1_sent';
25 | static const String reminder2Sent = 'reminder2_sent';
26 | static const String reminder3Sent = 'reminder3_sent';
27 | static const String reminderLastSent = 'reminder_last_sent';
28 | static const String exchangeRate = 'exchange_rate';
29 | static const String remainingCycles = 'remaining_cycles';
30 | static const String frequency = 'frequency';
31 | static const String nextSendDate = 'next_send_date';
32 | static const String lastSentDate = 'last_sent_date';
33 | static const String autoBill = 'auto_bill';
34 | }
35 |
--------------------------------------------------------------------------------
/lib/ui/invoice/view/invoice_view_activity.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/ui/app/lists/activity_list_tile.dart';
6 | import 'package:invoiceninja_flutter/ui/app/lists/list_divider.dart';
7 | import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
8 | import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart';
9 | import 'package:invoiceninja_flutter/ui/invoice/view/invoice_view_vm.dart';
10 |
11 | class InvoiceViewActivity extends StatefulWidget {
12 | const InvoiceViewActivity({Key key, this.viewModel}) : super(key: key);
13 |
14 | final AbstractInvoiceViewVM viewModel;
15 |
16 | @override
17 | _InvoiceViewActivityState createState() => _InvoiceViewActivityState();
18 | }
19 |
20 | class _InvoiceViewActivityState extends State {
21 | @override
22 | void didChangeDependencies() {
23 | if (widget.viewModel.invoice.isStale) {
24 | widget.viewModel.onRefreshed(context);
25 | }
26 | super.didChangeDependencies();
27 | }
28 |
29 | @override
30 | Widget build(BuildContext context) {
31 | final invoice = widget.viewModel.invoice;
32 | final activities = invoice.activities;
33 |
34 | if (!invoice.isLoaded) {
35 | return LoadingIndicator();
36 | }
37 |
38 | return ScrollableListViewBuilder(
39 | itemCount: activities.length,
40 | padding: const EdgeInsets.symmetric(vertical: 16),
41 | separatorBuilder: (context, index) => ListDivider(),
42 | itemBuilder: (BuildContext context, index) {
43 | final activity = activities[index];
44 | return ActivityListTile(
45 | activity: activity,
46 | enableNavigation: false,
47 | );
48 | },
49 | );
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/lib/ui/app/copy_to_clipboard.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/services.dart';
3 | import 'package:flutter_styled_toast/flutter_styled_toast.dart';
4 | import 'package:invoiceninja_flutter/utils/localization.dart';
5 |
6 | class CopyToClipboard extends StatelessWidget {
7 | const CopyToClipboard({
8 | Key key,
9 | @required this.value,
10 | this.child,
11 | this.showBorder = false,
12 | this.onLongPress,
13 | this.prefix,
14 | }) : super(key: key);
15 |
16 | final Widget child;
17 | final String value;
18 | final bool showBorder;
19 | final Function onLongPress;
20 | final String prefix;
21 |
22 | @override
23 | Widget build(BuildContext context) {
24 | if ((value ?? '').isEmpty) {
25 | return SizedBox();
26 | }
27 |
28 | final widget = child == null
29 | ? Text(
30 | prefix != null ? '$prefix: $value' : value,
31 | maxLines: 1,
32 | overflow: TextOverflow.ellipsis,
33 | )
34 | : child;
35 | final localization = AppLocalization.of(context);
36 | final onTap = () {
37 | Clipboard.setData(ClipboardData(text: value));
38 | showToast(
39 | localization.copiedToClipboard.replaceFirst(
40 | ':value',
41 | value.replaceAll('\n', ' '),
42 | ),
43 | );
44 | };
45 |
46 | if (showBorder) {
47 | return ConstrainedBox(
48 | child: OutlinedButton(
49 | onPressed: onTap,
50 | child: widget,
51 | onLongPress: onLongPress,
52 | ),
53 | constraints: BoxConstraints(maxWidth: 180),
54 | );
55 | } else {
56 | return InkWell(
57 | child: widget,
58 | onTap: onTap,
59 | onLongPress: onLongPress,
60 | );
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/lib/ui/recurring_invoice/edit/recurring_invoice_edit_pdf_vm.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Package imports:
5 | import 'package:flutter_redux/flutter_redux.dart';
6 | import 'package:redux/redux.dart';
7 |
8 | // Project imports:
9 | import 'package:invoiceninja_flutter/data/models/models.dart';
10 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
11 | import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_pdf.dart';
12 | import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_pdf_vm.dart';
13 |
14 | class RecurringInvoiceEditPDFScreen extends StatelessWidget {
15 | const RecurringInvoiceEditPDFScreen({Key key}) : super(key: key);
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return StoreConnector(
20 | converter: (Store store) {
21 | return RecurringInvoiceEditPDFVM.fromStore(store);
22 | },
23 | builder: (context, viewModel) {
24 | return InvoiceEditPDF(
25 | viewModel: viewModel,
26 | );
27 | },
28 | );
29 | }
30 | }
31 |
32 | class RecurringInvoiceEditPDFVM extends EntityEditPDFVM {
33 | RecurringInvoiceEditPDFVM({
34 | @required CompanyEntity company,
35 | @required InvoiceEntity invoice,
36 | @required AppState state,
37 | }) : super(
38 | company: company,
39 | invoice: invoice,
40 | state: state,
41 | );
42 |
43 | factory RecurringInvoiceEditPDFVM.fromStore(Store store) {
44 | final AppState state = store.state;
45 | final invoice = state.recurringInvoiceUIState.editing;
46 |
47 | return RecurringInvoiceEditPDFVM(
48 | company: state.company,
49 | invoice: invoice,
50 | state: state,
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Run Tests
2 | on:
3 | push:
4 | branches:
5 | - master
6 |
7 | jobs:
8 |
9 | test:
10 | runs-on: macos-latest
11 | #sequence of tasks called
12 | steps:
13 | - uses: actions/checkout@v1
14 | # Setup a flutter environment.
15 | # https://github.com/marketplace/actions/flutter-action
16 | - uses: subosito/flutter-action@v1
17 | with:
18 | #flutter-version: '2.2.3'
19 | channel: 'beta'
20 | - run: flutter pub get
21 | - run: cp lib/.env.dart.example lib/.env.dart
22 | - run: flutter analyze
23 |
24 | drive_ios:
25 | strategy:
26 | matrix:
27 | device:
28 | - "iPhone 11 Pro (14.4)"
29 | - "iPad Pro (9.7-inch) (14.4)"
30 | fail-fast: false
31 | runs-on: macos-latest
32 | needs: test
33 | steps:
34 | - name: List all simulators
35 | run: xcrun instruments -s
36 | # get UUID simulator and boot a simulator on mac from command line
37 | - name: Start Simulator
38 | run: |
39 | UDID=$(
40 | xcrun instruments -s |
41 | awk \
42 | -F ' *[][]' \
43 | -v 'device=${{ matrix.device }}' \
44 | '$1 == device { print $2 }'
45 | )
46 | xcrun simctl boot "${UDID:?No Simulator with this name found}"
47 | - uses: actions/checkout@v1
48 | - uses: subosito/flutter-action@v1
49 | with:
50 | #flutter-version: '2.2.3'
51 | channel: 'beta'
52 | - name: Setup App
53 | run: |
54 | cp lib/.env.dart.example lib/.env.dart
55 | echo ${{secrets.firebase_ios}} | base64 --decode > ios/Runner/GoogleService-Info.plist
56 | - name: Run iOS Flutter Driver tests
57 | run: flutter drive --target=test_driver/login_it.dart
--------------------------------------------------------------------------------
/lib/ui/app/forms/design_picker.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Package imports:
5 | import 'package:flutter_redux/flutter_redux.dart';
6 |
7 | // Project imports:
8 | import 'package:invoiceninja_flutter/data/models/design_model.dart';
9 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
10 | import 'package:invoiceninja_flutter/ui/app/forms/app_dropdown_button.dart';
11 | import 'package:invoiceninja_flutter/utils/localization.dart';
12 |
13 | class DesignPicker extends StatelessWidget {
14 | const DesignPicker({
15 | @required this.onSelected,
16 | this.label,
17 | this.initialValue,
18 | });
19 |
20 | final Function(DesignEntity) onSelected;
21 | final String label;
22 | final String initialValue;
23 |
24 | @override
25 | Widget build(BuildContext context) {
26 | final localization = AppLocalization.of(context);
27 | final store = StoreProvider.of(context);
28 | final state = store.state;
29 | final designState = state.designState;
30 |
31 | return AppDropdownButton(
32 | value: initialValue,
33 | onChanged: (dynamic value) => onSelected(designState.map[value]),
34 | items: designState.list
35 | .where((designId) {
36 | final design = designState.map[designId];
37 | if (state.isHosted &&
38 | !state.isPaidAccount &&
39 | !state.account.isTrial &&
40 | !design.isFree) {
41 | return false;
42 | }
43 | return design.isActive || designId == initialValue;
44 | })
45 | .map((value) => DropdownMenuItem(
46 | value: value,
47 | child: Text(designState.map[value].displayName),
48 | ))
49 | .toList(),
50 | labelText: label ?? localization.design,
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/lib/ui/app/forms/save_cancel_buttons.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/ui/app/buttons/app_text_button.dart';
6 | import 'package:invoiceninja_flutter/utils/localization.dart';
7 |
8 | class SaveCancelButtons extends StatelessWidget {
9 | const SaveCancelButtons({
10 | this.onSavePressed,
11 | this.onCancelPressed,
12 | this.saveLabel,
13 | this.cancelLabel,
14 | this.isHeader = true,
15 | this.isEnabled = true,
16 | this.isCancelEnabled = false,
17 | });
18 |
19 | final bool isEnabled;
20 | final bool isCancelEnabled;
21 | final String saveLabel;
22 | final String cancelLabel;
23 | final bool isHeader;
24 | final Function(BuildContext) onCancelPressed;
25 | final Function(BuildContext) onSavePressed;
26 |
27 | @override
28 | Widget build(BuildContext context) {
29 | final localization = AppLocalization.of(context);
30 |
31 | return Row(
32 | crossAxisAlignment:
33 | isHeader ? CrossAxisAlignment.stretch : CrossAxisAlignment.center,
34 | children: [
35 | if (onCancelPressed != null)
36 | Builder(builder: (BuildContext context) {
37 | return AppTextButton(
38 | label: cancelLabel ?? localization.cancel,
39 | isInHeader: isHeader && (isEnabled || isCancelEnabled),
40 | onPressed: isEnabled || isCancelEnabled
41 | ? () => onCancelPressed(context)
42 | : null,
43 | );
44 | }),
45 | Builder(builder: (BuildContext context) {
46 | return AppTextButton(
47 | label: saveLabel ?? localization.save,
48 | isInHeader: isHeader,
49 | onPressed: isEnabled ? () => onSavePressed(context) : null,
50 | );
51 | }),
52 | ],
53 | );
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/snap/snapcraft.yaml:
--------------------------------------------------------------------------------
1 | name: invoiceninja
2 | version: '5.0.85'
3 | summary: Create invoices, accept payments, track expenses & time-tasks
4 | description: "### Note: if the app fails to run using `snap run invoiceninja` it may help to run `/snap/invoiceninja/current/bin/invoiceninja` instead
5 |
6 | Create. Send. Get Paid.
7 |
8 | Invoice Ninja is a leading source-code available platform for SMB’s to invoice, accept payments, track expenses & time billable-tasks. Designed for freelancers and small to medium size businesses, Invoice Ninja is a suite of apps to help you get paid.
9 |
10 | • Incredibly easy to use
11 | Invoice Ninja was built to serve freelancers and business owners with a complete suite of invoicing & payment tools to advance your business.
12 |
13 | • Invoicing & Payments
14 | Every feature is geared towards accurate and secure invoicing and getting you paid. With Invoice Ninja you can send beautiful branded invoices with minimum of effort and maximum professionalism.
15 |
16 | • Time Tracker & Projects
17 | Create projects and individual tasks per project. When done, simply “Send task to invoice” and all details will be sent ready for your clients to pay!
18 |
19 | • Track Vendors & Expenses
20 | With Invoice Ninja, all your earnings, expenses, clients and vendors are stored and managed in one system. Categorize your vendors & re-invoice expenses to clients, or simply run expense reports.
21 |
22 | All of these features combine to help you receive the money you deserve and reduce the amount of time you spend on repetitive invoicing tasks. Spend less time on paperwork and more time at your craft."
23 | confinement: strict
24 | base: core18
25 | grade: stable
26 |
27 | apps:
28 | invoiceninja:
29 | command: invoiceninja
30 | extensions: [flutter-stable]
31 | plugs:
32 | - network
33 |
34 | parts:
35 | invoiceninja:
36 | source: .
37 | plugin: flutter
38 | flutter-target: lib/main.dart
--------------------------------------------------------------------------------
/lib/ui/invoice/edit/invoice_edit_pdf_vm.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Package imports:
5 | import 'package:flutter_redux/flutter_redux.dart';
6 | import 'package:redux/redux.dart';
7 |
8 | // Project imports:
9 | import 'package:invoiceninja_flutter/data/models/models.dart';
10 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
11 | import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_pdf.dart';
12 |
13 | class InvoiceEditPDFScreen extends StatelessWidget {
14 | const InvoiceEditPDFScreen({Key key}) : super(key: key);
15 |
16 | @override
17 | Widget build(BuildContext context) {
18 | return StoreConnector(
19 | converter: (Store store) {
20 | return InvoiceEditPDFVM.fromStore(store);
21 | },
22 | builder: (context, viewModel) {
23 | return InvoiceEditPDF(
24 | viewModel: viewModel,
25 | );
26 | },
27 | );
28 | }
29 | }
30 |
31 | class EntityEditPDFVM {
32 | EntityEditPDFVM({
33 | @required this.state,
34 | @required this.company,
35 | @required this.invoice,
36 | });
37 |
38 | final AppState state;
39 | final CompanyEntity company;
40 | final InvoiceEntity invoice;
41 | }
42 |
43 | class InvoiceEditPDFVM extends EntityEditPDFVM {
44 | InvoiceEditPDFVM({
45 | @required CompanyEntity company,
46 | @required InvoiceEntity invoice,
47 | @required AppState state,
48 | }) : super(
49 | company: company,
50 | invoice: invoice,
51 | state: state,
52 | );
53 |
54 | factory InvoiceEditPDFVM.fromStore(Store store) {
55 | final AppState state = store.state;
56 | final invoice = state.invoiceUIState.editing;
57 |
58 | return InvoiceEditPDFVM(
59 | company: state.company,
60 | invoice: invoice,
61 | state: state,
62 | );
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/lib/ui/document/document_screen_vm.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter/widgets.dart';
4 |
5 | // Package imports:
6 | import 'package:built_collection/built_collection.dart';
7 | import 'package:flutter_redux/flutter_redux.dart';
8 | import 'package:redux/redux.dart';
9 |
10 | // Project imports:
11 | import 'package:invoiceninja_flutter/data/models/models.dart';
12 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
13 | import 'package:invoiceninja_flutter/redux/document/document_selectors.dart';
14 | import 'document_screen.dart';
15 |
16 | class DocumentScreenBuilder extends StatelessWidget {
17 | const DocumentScreenBuilder({Key key}) : super(key: key);
18 |
19 | @override
20 | Widget build(BuildContext context) {
21 | return StoreConnector(
22 | converter: DocumentScreenVM.fromStore,
23 | builder: (context, vm) {
24 | return DocumentScreen(
25 | viewModel: vm,
26 | );
27 | },
28 | );
29 | }
30 | }
31 |
32 | class DocumentScreenVM {
33 | DocumentScreenVM({
34 | @required this.isInMultiselect,
35 | @required this.documentList,
36 | @required this.userCompany,
37 | @required this.documentMap,
38 | });
39 |
40 | final bool isInMultiselect;
41 | final UserCompanyEntity userCompany;
42 | final List documentList;
43 | final BuiltMap documentMap;
44 |
45 | static DocumentScreenVM fromStore(Store store) {
46 | final state = store.state;
47 |
48 | return DocumentScreenVM(
49 | documentMap: state.documentState.map,
50 | documentList: memoizedFilteredDocumentList(state.documentState.map,
51 | state.documentState.list, state.documentListState),
52 | userCompany: state.userCompany,
53 | isInMultiselect: state.documentListState.isInMultiselect(),
54 | );
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/lib/redux/auth/auth_state.dart:
--------------------------------------------------------------------------------
1 | // Package imports:
2 | import 'package:built_value/built_value.dart';
3 | import 'package:built_value/serializer.dart';
4 |
5 | // Project imports:
6 | import 'package:invoiceninja_flutter/constants.dart';
7 | import 'package:invoiceninja_flutter/utils/formatting.dart';
8 |
9 | part 'auth_state.g.dart';
10 |
11 | abstract class AuthState implements Built {
12 | factory AuthState({String url, String referralCode}) {
13 | return _$AuthState._(
14 | email: '',
15 | url: url ?? '',
16 | isAuthenticated: false,
17 | isInitialized: false,
18 | lastEnteredPasswordAt: 0,
19 | referralCode: referralCode ?? '',
20 | );
21 | }
22 |
23 | AuthState._();
24 |
25 | @override
26 | @memoized
27 | int get hashCode;
28 |
29 | String get email;
30 |
31 | String get url;
32 |
33 | bool get isInitialized;
34 |
35 | bool get isAuthenticated;
36 |
37 | int get lastEnteredPasswordAt;
38 |
39 | String get referralCode;
40 |
41 | bool get isHosted {
42 | final cleanUrl = cleanApiUrl(url);
43 |
44 | if (cleanUrl.isEmpty) {
45 | return true;
46 | }
47 |
48 | if ([
49 | kAppProductionUrl,
50 | kAppDemoUrl,
51 | kAppStagingUrl,
52 | ].contains(cleanUrl)) {
53 | return true;
54 | }
55 |
56 | // Handle if a user logs in with their subdomain
57 | if (cleanUrl.endsWith('.invoicing.co')) {
58 | return true;
59 | }
60 |
61 | return false;
62 | }
63 |
64 | bool get isSelfHost => !isHosted;
65 |
66 | bool get isStaging => cleanApiUrl(url) == kAppStagingUrl;
67 |
68 | bool get isLargeTest => cleanApiUrl(url) == kAppLargeTestUrl;
69 |
70 | // ignore: unused_element
71 | static void _initializeBuilder(AuthStateBuilder builder) =>
72 | builder..referralCode = '';
73 |
74 | static Serializer get serializer => _$authStateSerializer;
75 | }
76 |
--------------------------------------------------------------------------------
/lib/ui/app/forms/app_form.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Package imports:
5 | import 'package:flutter_redux/flutter_redux.dart';
6 |
7 | // Project imports:
8 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
9 | import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart';
10 |
11 | class AppForm extends StatelessWidget {
12 | const AppForm({
13 | this.children,
14 | this.child,
15 | @required this.formKey,
16 | @required this.focusNode,
17 | });
18 |
19 | final GlobalKey formKey;
20 | final List children;
21 | final Widget child;
22 | final FocusScopeNode focusNode;
23 |
24 | @override
25 | Widget build(BuildContext context) {
26 | return FocusScope(
27 | node: focusNode,
28 | child: Form(
29 | key: formKey,
30 | child: child ??
31 | ScrollableListView(
32 | children: children,
33 | ),
34 | ),
35 | );
36 | }
37 | }
38 |
39 | class AppTabForm extends StatelessWidget {
40 | const AppTabForm({
41 | @required this.children,
42 | @required this.formKey,
43 | @required this.focusNode,
44 | @required this.tabController,
45 | this.tabBarKey,
46 | });
47 |
48 | final FocusScopeNode focusNode;
49 | final GlobalKey formKey;
50 | final List children;
51 | final TabController tabController;
52 | final Key tabBarKey;
53 |
54 | @override
55 | Widget build(BuildContext context) {
56 | final state = StoreProvider.of(context).state;
57 |
58 | return FocusScope(
59 | node: focusNode,
60 | child: Form(
61 | key: formKey,
62 | child: TabBarView(
63 | key: tabBarKey ?? ValueKey(state.settingsUIState.updatedAt),
64 | children: children,
65 | controller: tabController,
66 | ),
67 | ),
68 | );
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/lib/ui/quote/edit/quote_edit_notes_vm.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Package imports:
5 | import 'package:flutter_redux/flutter_redux.dart';
6 | import 'package:redux/redux.dart';
7 |
8 | // Project imports:
9 | import 'package:invoiceninja_flutter/data/models/models.dart';
10 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
11 | import 'package:invoiceninja_flutter/redux/quote/quote_actions.dart';
12 | import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_notes.dart';
13 | import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_notes_vm.dart';
14 |
15 | class QuoteEditNotesScreen extends StatelessWidget {
16 | const QuoteEditNotesScreen({Key key}) : super(key: key);
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return StoreConnector(
21 | converter: (Store store) {
22 | return QuoteEditNotesVM.fromStore(store);
23 | },
24 | builder: (context, viewModel) {
25 | return InvoiceEditNotes(
26 | viewModel: viewModel,
27 | );
28 | },
29 | );
30 | }
31 | }
32 |
33 | class QuoteEditNotesVM extends EntityEditNotesVM {
34 | QuoteEditNotesVM({
35 | CompanyEntity company,
36 | InvoiceEntity invoice,
37 | Function(InvoiceEntity) onChanged,
38 | AppState state,
39 | }) : super(
40 | company: company,
41 | invoice: invoice,
42 | onChanged: onChanged,
43 | state: state,
44 | );
45 |
46 | factory QuoteEditNotesVM.fromStore(Store store) {
47 | final AppState state = store.state;
48 | final quote = state.quoteUIState.editing;
49 |
50 | return QuoteEditNotesVM(
51 | company: state.company,
52 | invoice: quote,
53 | onChanged: (InvoiceEntity quote) => store.dispatch(UpdateQuote(quote)),
54 | state: state,
55 | );
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/lib/ui/quote/quote_pdf_vm.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Package imports:
5 | import 'package:flutter_redux/flutter_redux.dart';
6 | import 'package:redux/redux.dart';
7 |
8 | // Project imports:
9 | import 'package:invoiceninja_flutter/data/models/models.dart';
10 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
11 | import 'package:invoiceninja_flutter/ui/invoice/invoice_pdf.dart';
12 | import 'package:invoiceninja_flutter/ui/invoice/invoice_pdf_vm.dart';
13 |
14 | class QuotePdfScreen extends StatelessWidget {
15 | const QuotePdfScreen({Key key, this.showAppBar = true}) : super(key: key);
16 |
17 | final bool showAppBar;
18 |
19 | static const String route = '/quote/pdf';
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 | return StoreConnector(
24 | converter: (Store store) {
25 | return QuotePdfVM.fromStore(store);
26 | },
27 | builder: (context, vm) {
28 | return InvoicePdfView(
29 | key: ValueKey('__quote_pdf_${vm.invoice.id}__'),
30 | viewModel: vm,
31 | showAppBar: showAppBar,
32 | );
33 | },
34 | );
35 | }
36 | }
37 |
38 | class QuotePdfVM extends EntityPdfVM {
39 | QuotePdfVM({
40 | AppState state,
41 | InvoiceEntity invoice,
42 | String activityId,
43 | }) : super(
44 | state: state,
45 | invoice: invoice,
46 | activityId: activityId,
47 | );
48 |
49 | factory QuotePdfVM.fromStore(Store store) {
50 | final state = store.state;
51 | final quoteUIState = state.uiState.quoteUIState;
52 | final invoiceId = quoteUIState.selectedId;
53 | final invoice = state.quoteState.get(invoiceId);
54 |
55 | return QuotePdfVM(
56 | state: state,
57 | invoice: invoice,
58 | activityId: quoteUIState.historyActivityId,
59 | );
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/lib/ui/user/user_screen_vm.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter/widgets.dart';
4 |
5 | // Package imports:
6 | import 'package:built_collection/built_collection.dart';
7 | import 'package:flutter_redux/flutter_redux.dart';
8 | import 'package:redux/redux.dart';
9 |
10 | // Project imports:
11 | import 'package:invoiceninja_flutter/data/models/models.dart';
12 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
13 | import 'package:invoiceninja_flutter/redux/user/user_selectors.dart';
14 | import 'user_screen.dart';
15 |
16 | class UserScreenBuilder extends StatelessWidget {
17 | const UserScreenBuilder({Key key}) : super(key: key);
18 |
19 | @override
20 | Widget build(BuildContext context) {
21 | return StoreConnector(
22 | converter: UserScreenVM.fromStore,
23 | builder: (context, vm) {
24 | return UserScreen(
25 | viewModel: vm,
26 | );
27 | },
28 | );
29 | }
30 | }
31 |
32 | class UserScreenVM {
33 | UserScreenVM({
34 | @required this.isInMultiselect,
35 | @required this.userList,
36 | @required this.userCompany,
37 | @required this.userMap,
38 | });
39 |
40 | final bool isInMultiselect;
41 | final UserCompanyEntity userCompany;
42 | final List userList;
43 | final BuiltMap userMap;
44 |
45 | static UserScreenVM fromStore(Store store) {
46 | final state = store.state;
47 |
48 | return UserScreenVM(
49 | userMap: state.userState.map,
50 | userList: memoizedFilteredUserList(
51 | state.getUISelection(EntityType.user),
52 | state.userState.map,
53 | state.userState.list,
54 | state.userListState,
55 | state.user.id),
56 | userCompany: state.userCompany,
57 | isInMultiselect: state.userListState.isInMultiselect(),
58 | );
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/lib/ui/app/web_session_timeout.dart:
--------------------------------------------------------------------------------
1 | // Dart imports:
2 | import 'dart:async';
3 |
4 | // Flutter imports:
5 | import 'package:flutter/foundation.dart';
6 | import 'package:flutter/material.dart';
7 |
8 | // Package imports:
9 | import 'package:flutter_redux/flutter_redux.dart';
10 |
11 | // Project imports:
12 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
13 | import 'package:invoiceninja_flutter/redux/auth/auth_actions.dart';
14 |
15 | // ignore: unused_import
16 | import 'package:invoiceninja_flutter/utils/web_stub.dart'
17 | if (dart.library.html) 'package:invoiceninja_flutter/utils/web.dart';
18 |
19 | class WebSessionTimeout extends StatefulWidget {
20 | const WebSessionTimeout({this.child});
21 |
22 | final Widget child;
23 |
24 | @override
25 | _WebSessionTimeoutState createState() => _WebSessionTimeoutState();
26 | }
27 |
28 | class _WebSessionTimeoutState extends State {
29 | Timer _timer;
30 |
31 | @override
32 | void initState() {
33 | super.initState();
34 |
35 | if (!kIsWeb) {
36 | return;
37 | }
38 |
39 | _timer = Timer.periodic(
40 | Duration(minutes: 1),
41 | (Timer timer) {
42 | final store = StoreProvider.of(context);
43 | final state = store.state;
44 | final sessionTimeout = state.company.sessionTimeout;
45 |
46 | if (sessionTimeout == 0) {
47 | return;
48 | }
49 |
50 | final sessionLength = DateTime.now().millisecondsSinceEpoch -
51 | state.userCompanyState.lastUpdated;
52 |
53 | if (sessionLength > sessionTimeout) {
54 | store.dispatch(UserLogout());
55 | }
56 | },
57 | );
58 | }
59 |
60 | @override
61 | void dispose() {
62 | _timer?.cancel();
63 | _timer = null;
64 | super.dispose();
65 | }
66 |
67 | @override
68 | Widget build(BuildContext context) {
69 | return widget.child;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/lib/ui/credit/edit/credit_edit_notes_vm.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Package imports:
5 | import 'package:flutter_redux/flutter_redux.dart';
6 | import 'package:redux/redux.dart';
7 |
8 | // Project imports:
9 | import 'package:invoiceninja_flutter/data/models/models.dart';
10 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
11 | import 'package:invoiceninja_flutter/redux/credit/credit_actions.dart';
12 | import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_notes.dart';
13 | import 'package:invoiceninja_flutter/ui/invoice/edit/invoice_edit_notes_vm.dart';
14 |
15 | class CreditEditNotesScreen extends StatelessWidget {
16 | const CreditEditNotesScreen({Key key}) : super(key: key);
17 |
18 | @override
19 | Widget build(BuildContext context) {
20 | return StoreConnector(
21 | converter: (Store store) {
22 | return CreditEditNotesVM.fromStore(store);
23 | },
24 | builder: (context, viewModel) {
25 | return InvoiceEditNotes(
26 | viewModel: viewModel,
27 | );
28 | },
29 | );
30 | }
31 | }
32 |
33 | class CreditEditNotesVM extends EntityEditNotesVM {
34 | CreditEditNotesVM({
35 | CompanyEntity company,
36 | InvoiceEntity invoice,
37 | Function(InvoiceEntity) onChanged,
38 | AppState state,
39 | }) : super(
40 | company: company,
41 | invoice: invoice,
42 | onChanged: onChanged,
43 | state: state,
44 | );
45 |
46 | factory CreditEditNotesVM.fromStore(Store store) {
47 | final AppState state = store.state;
48 | final credit = state.creditUIState.editing;
49 |
50 | return CreditEditNotesVM(
51 | company: state.company,
52 | invoice: credit,
53 | onChanged: (InvoiceEntity credit) => store.dispatch(UpdateCredit(credit)),
54 | state: state,
55 | );
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/lib/ui/credit/credit_pdf_vm.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Package imports:
5 | import 'package:flutter_redux/flutter_redux.dart';
6 | import 'package:redux/redux.dart';
7 |
8 | // Project imports:
9 | import 'package:invoiceninja_flutter/data/models/models.dart';
10 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
11 | import 'package:invoiceninja_flutter/ui/invoice/invoice_pdf.dart';
12 | import 'package:invoiceninja_flutter/ui/invoice/invoice_pdf_vm.dart';
13 |
14 | class CreditPdfScreen extends StatelessWidget {
15 | const CreditPdfScreen({Key key, this.showAppBar = true}) : super(key: key);
16 |
17 | final bool showAppBar;
18 |
19 | static const String route = '/credit/pdf';
20 |
21 | @override
22 | Widget build(BuildContext context) {
23 | return StoreConnector(
24 | converter: (Store store) {
25 | return CreditPdfVM.fromStore(store);
26 | },
27 | builder: (context, vm) {
28 | return InvoicePdfView(
29 | key: ValueKey('__credit_pdf_${vm.invoice.id}__'),
30 | viewModel: vm,
31 | showAppBar: showAppBar,
32 | );
33 | },
34 | );
35 | }
36 | }
37 |
38 | class CreditPdfVM extends EntityPdfVM {
39 | CreditPdfVM({
40 | AppState state,
41 | InvoiceEntity invoice,
42 | String activityId,
43 | }) : super(
44 | state: state,
45 | invoice: invoice,
46 | activityId: activityId,
47 | );
48 |
49 | factory CreditPdfVM.fromStore(Store store) {
50 | final state = store.state;
51 | final creditUIState = state.uiState.creditUIState;
52 | final invoiceId = creditUIState.selectedId;
53 | final invoice = state.creditState.get(invoiceId);
54 |
55 | return CreditPdfVM(
56 | state: state,
57 | invoice: invoice,
58 | activityId: creditUIState.historyActivityId,
59 | );
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/lib/utils/designs.dart:
--------------------------------------------------------------------------------
1 | // Dart imports:
2 | import 'dart:convert';
3 |
4 | // Flutter imports:
5 | import 'package:flutter/cupertino.dart';
6 |
7 | // Package imports:
8 | import 'package:flutter_redux/flutter_redux.dart';
9 | import 'package:http/http.dart';
10 |
11 | // Project imports:
12 | import 'package:invoiceninja_flutter/.env.dart';
13 | import 'package:invoiceninja_flutter/data/models/design_model.dart';
14 | import 'package:invoiceninja_flutter/data/models/serializers.dart';
15 | import 'package:invoiceninja_flutter/data/web_client.dart';
16 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
17 | import 'dialogs.dart';
18 |
19 | void loadDesign({
20 | @required BuildContext context,
21 | @required DesignEntity design,
22 | @required bool isDraftMode,
23 | @required Function(Response) onComplete,
24 | }) {
25 | if (Config.DEMO_MODE) {
26 | onComplete(null);
27 | return;
28 | }
29 |
30 | final webClient = WebClient();
31 | final state = StoreProvider.of(context).state;
32 | final credentials = state.credentials;
33 | String url = '${credentials.url}/preview';
34 |
35 | if (isDraftMode) {
36 | url += '?html=true';
37 | }
38 |
39 | final request = DesignPreviewRequest(design: design);
40 | final data =
41 | serializers.serializeWith(DesignPreviewRequest.serializer, request);
42 |
43 | webClient
44 | .post(url, credentials.token, data: json.encode(data), rawResponse: true)
45 | .then((dynamic response) {
46 | if ((response as Response).statusCode >= 400) {
47 | showErrorDialog(
48 | context: context,
49 | message:
50 | '${(response as Response).statusCode}: ${(response as Response).reasonPhrase}');
51 | onComplete(null);
52 | } else {
53 | onComplete(response);
54 | }
55 | }).catchError((dynamic error) {
56 | showErrorDialog(context: context, message: '$error');
57 | onComplete(null);
58 | });
59 | }
60 |
--------------------------------------------------------------------------------
/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 | std::string utf8_string;
52 | if (target_length == 0 || target_length > utf8_string.max_size()) {
53 | return utf8_string;
54 | }
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 |
--------------------------------------------------------------------------------
/lib/ui/app/forms/client_picker.dart:
--------------------------------------------------------------------------------
1 | // Dart imports:
2 | import 'dart:async';
3 |
4 | // Flutter imports:
5 | import 'package:flutter/material.dart';
6 |
7 | // Package imports:
8 | import 'package:flutter_redux/flutter_redux.dart';
9 |
10 | // Project imports:
11 | import 'package:invoiceninja_flutter/data/models/entities.dart';
12 | import 'package:invoiceninja_flutter/redux/app/app_state.dart';
13 | import 'package:invoiceninja_flutter/redux/client/client_selectors.dart';
14 | import 'package:invoiceninja_flutter/redux/client/client_state.dart';
15 | import 'package:invoiceninja_flutter/ui/app/entity_dropdown.dart';
16 | import 'package:invoiceninja_flutter/utils/localization.dart';
17 |
18 | class ClientPicker extends StatelessWidget {
19 | const ClientPicker({
20 | @required this.clientId,
21 | @required this.clientState,
22 | @required this.onSelected,
23 | @required this.onAddPressed,
24 | this.autofocus,
25 | });
26 |
27 | final String clientId;
28 | final ClientState clientState;
29 | final Function(SelectableEntity) onSelected;
30 | final Function(Completer completer) onAddPressed;
31 | final bool autofocus;
32 |
33 | @override
34 | Widget build(BuildContext context) {
35 | final localization = AppLocalization.of(context);
36 | final store = StoreProvider.of(context);
37 | final state = store.state;
38 |
39 | return EntityDropdown(
40 | entityType: EntityType.client,
41 | labelText: localization.client,
42 | entityId: clientId,
43 | autofocus: autofocus,
44 | entityList: memoizedDropdownClientList(clientState.map, clientState.list,
45 | state.userState.map, state.staticState),
46 | entityMap: clientState.map,
47 | validator: (String val) => val.trim().isEmpty
48 | ? AppLocalization.of(context).pleaseSelectAClient
49 | : null,
50 | onSelected: onSelected,
51 | onAddPressed: onAddPressed,
52 | );
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/lib/ui/app/invoice/tax_rate_field.dart:
--------------------------------------------------------------------------------
1 | // Flutter imports:
2 | import 'package:flutter/material.dart';
3 |
4 | // Project imports:
5 | import 'package:invoiceninja_flutter/constants.dart';
6 | import 'package:invoiceninja_flutter/ui/app/forms/decorated_form_field.dart';
7 | import 'package:invoiceninja_flutter/utils/formatting.dart';
8 | import 'package:invoiceninja_flutter/utils/localization.dart';
9 |
10 | class TaxRateField extends StatelessWidget {
11 | const TaxRateField({
12 | Key key,
13 | @required this.onNameChanged,
14 | @required this.onAmountChanged,
15 | @required this.initialTaxName,
16 | @required this.initialTaxAmount,
17 | }) : super(key: key);
18 |
19 | final Function(String) onNameChanged;
20 | final Function(double) onAmountChanged;
21 | final String initialTaxName;
22 | final double initialTaxAmount;
23 |
24 | @override
25 | Widget build(BuildContext context) {
26 | final localization = AppLocalization.of(context);
27 |
28 | return Row(
29 | children: [
30 | Expanded(
31 | child: DecoratedFormField(
32 | label: localization.taxName,
33 | initialValue: initialTaxName,
34 | onChanged: (value) => onNameChanged(value),
35 | keyboardType: TextInputType.text,
36 | ),
37 | ),
38 | SizedBox(
39 | width: kTableColumnGap,
40 | ),
41 | Expanded(
42 | child: DecoratedFormField(
43 | label: localization.taxAmount,
44 | initialValue: formatNumber(
45 | initialTaxAmount,
46 | context,
47 | formatNumberType: FormatNumberType.inputMoney,
48 | ),
49 | keyboardType:
50 | TextInputType.numberWithOptions(decimal: true, signed: true),
51 | onChanged: (value) => onAmountChanged(parseDouble(value)),
52 | //textAlign: TextAlign.end,
53 | ),
54 | ),
55 | ],
56 | );
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/lib/data/models/quote_model.dart:
--------------------------------------------------------------------------------
1 | class QuoteFields {
2 | static const String total = 'total';
3 | static const String amount = 'amount';
4 | static const String balanceDue = 'balance_due';
5 | static const String clientId = 'client_id';
6 | static const String client = 'client';
7 | static const String status = 'status';
8 | static const String statusId = 'status_id';
9 | static const String number = 'number';
10 | static const String discount = 'discount';
11 | static const String poNumber = 'po_number';
12 | static const String date = 'date';
13 | static const String validUntil = 'valid_until';
14 | static const String lastSentDate = 'last_sent_date';
15 | static const String terms = 'terms';
16 | static const String footer = 'footer';
17 | static const String partial = 'partial';
18 | static const String partialDueDate = 'partial_due_date';
19 | static const String publicNotes = 'public_notes';
20 | static const String privateNotes = 'private_notes';
21 | static const String customValue1 = 'custom1';
22 | static const String customValue2 = 'custom2';
23 | static const String customValue3 = 'custom3';
24 | static const String customValue4 = 'custom4';
25 | static const String updatedAt = 'updated_at';
26 | static const String archivedAt = 'archived_at';
27 | static const String isDeleted = 'is_deleted';
28 | static const String documents = 'documents';
29 | static const String taxAmount = 'tax_amount';
30 | static const String exchangeRate = 'exchange_rate';
31 | static const String isViewed = 'is_viewed';
32 | static const String project = 'project';
33 | static const String vendor = 'vendor';
34 | static const String contactName = 'contact_name';
35 | static const String contactEmail = 'contact_email';
36 | static const String clientCity = 'client_city';
37 | static const String clientState = 'client_state';
38 | static const String clientPostalCode = 'client_postal_code';
39 | static const String clientCountry = 'client_country';
40 | }
41 |
--------------------------------------------------------------------------------