├── .gitattributes ├── .github └── workflows │ ├── docker-image-ali.yml │ ├── docker-image-all-ali.yml │ ├── docker-image-all-no-mysql-ali.yml │ ├── docker-image-all-no-mysql.yml │ ├── docker-image-all.yml │ └── docker-image.yml ├── .gitignore ├── .metadata ├── Dockerfile ├── Dockerfile2 ├── Dockerfile3 ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── cn │ │ │ │ └── biq │ │ │ │ └── moneynote │ │ │ │ └── MainActivity.java │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── locales │ ├── en_US.json │ └── zh_CN.json ├── logo.png └── logo.svg ├── docker-compose.yml ├── docker ├── gzip.conf └── nginx.conf.template ├── flutter_launcher_icons.yaml ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-50x50@1x.png │ │ │ ├── Icon-App-50x50@2x.png │ │ │ ├── Icon-App-57x57@1x.png │ │ │ ├── Icon-App-57x57@2x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-72x72@1x.png │ │ │ ├── Icon-App-72x72@2x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h └── RunnerTests │ └── RunnerTests.swift ├── lib ├── app.dart ├── app │ ├── bindings │ │ └── initial_binding.dart │ ├── core │ │ ├── base │ │ │ ├── base_controller.dart │ │ │ ├── base_repository.dart │ │ │ └── enums.dart │ │ ├── commons │ │ │ ├── form │ │ │ │ ├── not_empty_formz.dart │ │ │ │ └── not_empty_num_formz.dart │ │ │ └── widget_util.dart │ │ ├── components │ │ │ ├── asterisk.dart │ │ │ ├── bottomsheet_container.dart │ │ │ ├── detail_item.dart │ │ │ ├── dialog_confirm.dart │ │ │ ├── flutter_tree-2.0.3 │ │ │ │ ├── flutter_tree.dart │ │ │ │ └── src │ │ │ │ │ ├── tree_node.dart │ │ │ │ │ ├── tree_node_data.dart │ │ │ │ │ └── tree_view.dart │ │ │ ├── form │ │ │ │ ├── my_form_date.dart │ │ │ │ ├── my_form_switch.dart │ │ │ │ ├── my_form_text.dart │ │ │ │ ├── my_option.dart │ │ │ │ ├── my_select.dart │ │ │ │ └── my_tree_option.dart │ │ │ ├── lazy_indexed_stack.dart │ │ │ ├── loading_icon.dart │ │ │ ├── my_form_page.dart │ │ │ ├── order_button.dart │ │ │ ├── pages │ │ │ │ ├── content_page.dart │ │ │ │ ├── empty_page.dart │ │ │ │ ├── error_page.dart │ │ │ │ ├── index.dart │ │ │ │ └── loading_page.dart │ │ │ ├── popup_menu.dart │ │ │ └── web_view_page.dart │ │ ├── utils │ │ │ ├── api_url.dart │ │ │ ├── language.dart │ │ │ ├── message.dart │ │ │ ├── theme.dart │ │ │ ├── token.dart │ │ │ ├── utils.dart │ │ │ └── widget_util.dart │ │ └── values │ │ │ ├── app_const.dart │ │ │ ├── app_text_styles.dart │ │ │ └── app_values.dart │ ├── modules │ │ ├── accounts │ │ │ ├── controllers │ │ │ │ ├── account_adjust_controller.dart │ │ │ │ ├── account_detail_controller.dart │ │ │ │ ├── account_form_controller.dart │ │ │ │ └── accounts_controller.dart │ │ │ ├── data │ │ │ │ └── account_repository.dart │ │ │ └── ui │ │ │ │ ├── account_adjust_page.dart │ │ │ │ ├── account_detail_page.dart │ │ │ │ ├── account_form_page.dart │ │ │ │ └── accounts_page.dart │ │ ├── charts │ │ │ ├── chart_filter_page.dart │ │ │ ├── chart_repository.dart │ │ │ ├── charts_controller.dart │ │ │ ├── charts_page.dart │ │ │ └── widgets │ │ │ │ ├── circular_legend.dart │ │ │ │ └── filter │ │ │ │ ├── account.dart │ │ │ │ ├── book.dart │ │ │ │ ├── category.dart │ │ │ │ ├── filter_tag.dart │ │ │ │ ├── filter_title.dart │ │ │ │ ├── index.dart │ │ │ │ ├── max_time.dart │ │ │ │ ├── min_time.dart │ │ │ │ └── payee.dart │ │ ├── common │ │ │ └── select │ │ │ │ ├── select_controller.dart │ │ │ │ ├── select_option.dart │ │ │ │ └── tree_select_option.dart │ │ ├── flows │ │ │ ├── controllers │ │ │ │ ├── flow_detail_controller.dart │ │ │ │ ├── flow_form_controller.dart │ │ │ │ └── flows_controller.dart │ │ │ ├── flow_detail_page.dart │ │ │ ├── flow_filter_page.dart │ │ │ ├── flow_form_page.dart │ │ │ ├── flow_repository.dart │ │ │ ├── flows_page.dart │ │ │ └── widgets │ │ │ │ ├── filter │ │ │ │ ├── account.dart │ │ │ │ ├── book.dart │ │ │ │ ├── category.dart │ │ │ │ ├── confirm.dart │ │ │ │ ├── filter_tag.dart │ │ │ │ ├── filter_title.dart │ │ │ │ ├── has_file.dart │ │ │ │ ├── include.dart │ │ │ │ ├── index.dart │ │ │ │ ├── max_amount.dart │ │ │ │ ├── max_time.dart │ │ │ │ ├── min_amount.dart │ │ │ │ ├── min_time.dart │ │ │ │ ├── notes.dart │ │ │ │ ├── payee.dart │ │ │ │ └── type.dart │ │ │ │ ├── flow_file_list.dart │ │ │ │ └── form │ │ │ │ ├── account.dart │ │ │ │ ├── amount.dart │ │ │ │ ├── book.dart │ │ │ │ ├── category.dart │ │ │ │ ├── confirm.dart │ │ │ │ ├── create_time.dart │ │ │ │ ├── form_tag.dart │ │ │ │ ├── form_title.dart │ │ │ │ ├── include.dart │ │ │ │ ├── index.dart │ │ │ │ ├── notes.dart │ │ │ │ ├── payee.dart │ │ │ │ ├── to_account.dart │ │ │ │ └── transfer_amount.dart │ │ ├── login │ │ │ ├── controllers │ │ │ │ ├── auth_controller.dart │ │ │ │ └── login_controller.dart │ │ │ ├── data │ │ │ │ └── login_repository.dart │ │ │ └── ui │ │ │ │ ├── login_page.dart │ │ │ │ └── widgets │ │ │ │ ├── api_url_input.dart │ │ │ │ ├── login_form.dart │ │ │ │ ├── password_name_input.dart │ │ │ │ ├── submit_btn.dart │ │ │ │ └── user_name_input.dart │ │ └── my │ │ │ ├── controllers │ │ │ ├── account_overview_controller.dart │ │ │ ├── api_version_controller.dart │ │ │ ├── language_controller.dart │ │ │ └── theme_controller.dart │ │ │ └── my_page.dart │ ├── network │ │ ├── http.dart │ │ └── http_client.dart │ └── routes │ │ ├── app_pages.dart │ │ └── app_routes.dart ├── flavors │ └── environment.dart ├── generated │ └── locales.g.dart ├── index_page.dart ├── main.dart ├── start_page.dart └── theme.dart ├── notes └── notes.txt ├── pubspec.lock ├── pubspec.yaml ├── screenshots ├── 1.jpg ├── 2.jpg └── 3.jpg ├── test └── widget_test.dart ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── index.html └── manifest.json └── web2 ├── .last_build_id ├── assets ├── AssetManifest.bin ├── AssetManifest.json ├── FontManifest.json ├── NOTICES ├── assets │ ├── logo.png │ └── logo.svg ├── fonts │ └── MaterialIcons-Regular.otf ├── packages │ └── fluttertoast │ │ └── assets │ │ ├── toastify.css │ │ └── toastify.js └── shaders │ └── ink_sparkle.frag ├── canvaskit ├── canvaskit.js ├── canvaskit.wasm ├── chromium │ ├── canvaskit.js │ └── canvaskit.wasm ├── skwasm.js ├── skwasm.wasm └── skwasm.worker.js ├── favicon.png ├── flutter.js ├── flutter_service_worker.js ├── icons ├── Icon-192.png ├── Icon-512.png ├── Icon-maskable-192.png └── Icon-maskable-512.png ├── index.html ├── main.dart.js ├── manifest.json └── version.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Always perform LF normalization 5 | *.dart text 6 | *.gradle text 7 | *.html text 8 | *.java text 9 | *.json text 10 | *.md text 11 | *.py text 12 | *.sh text 13 | *.txt text 14 | *.xml text 15 | *.yaml text 16 | 17 | # Make sure that these Windows files always have CRLF line endings at checkout 18 | *.bat text eol=crlf 19 | *.ps1 text eol=crlf 20 | *.rc text eol=crlf 21 | *.sln text eol=crlf 22 | *.props text eol=crlf 23 | *.vcxproj text eol=crlf 24 | *.vcxproj.filters text eol=crlf 25 | # Including templates 26 | *.sln.tmpl text eol=crlf 27 | *.props.tmpl text eol=crlf 28 | *.vcxproj.tmpl text eol=crlf 29 | 30 | # Never perform LF normalization 31 | *.ico binary 32 | *.jar binary 33 | *.png binary 34 | *.zip binary 35 | *.ttf binary 36 | *.otf binary -------------------------------------------------------------------------------- /.github/workflows/docker-image-ali.yml: -------------------------------------------------------------------------------- 1 | name: docker acr image 2 | 3 | # 触发条件:在 push 到 main 分支后 4 | on: 5 | push: 6 | branches: 7 | - main 8 | paths: 9 | - 'web2/**' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | - name: Free Disk space 18 | uses: jlumbroso/free-disk-space@main 19 | with: 20 | # this might remove tools that are actually needed, 21 | # if set to "true" but frees about 6 GB 22 | tool-cache: false 23 | 24 | # all of these default to true, but feel free to set to 25 | # "false" if necessary for your workflow 26 | android: true 27 | dotnet: true 28 | haskell: true 29 | large-packages: true 30 | docker-images: true 31 | swap-storage: true 32 | - name: Set up QEMU 33 | uses: docker/setup-qemu-action@v3 34 | - name: Set up Docker Buildx 35 | uses: docker/setup-buildx-action@v3 36 | - name: Login to Aliyun Container Registry (ACR) 37 | uses: docker/login-action@v3 38 | with: 39 | registry: registry.cn-hangzhou.aliyuncs.com 40 | # region-id: cn-hangzhou # 3 41 | username: "${{ secrets.ACR_USERNAME }}" 42 | password: "${{ secrets.ACR_PASSWORD }}" 43 | - name: Build and Push Docker Image 44 | uses: docker/build-push-action@v5 45 | with: 46 | context: . 47 | platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v6 48 | push: true 49 | tags: registry.cn-hangzhou.aliyuncs.com/moneynote/moneynote-h5:latest 50 | -------------------------------------------------------------------------------- /.github/workflows/docker-image-all-ali.yml: -------------------------------------------------------------------------------- 1 | name: all acr image 2 | 3 | # 触发条件:在 push 到 main 分支后 4 | on: 5 | push: 6 | branches: 7 | - main1 8 | paths: 9 | - 'web2/**' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | - name: Free Disk space 18 | uses: jlumbroso/free-disk-space@main 19 | with: 20 | # this might remove tools that are actually needed, 21 | # if set to "true" but frees about 6 GB 22 | tool-cache: false 23 | 24 | # all of these default to true, but feel free to set to 25 | # "false" if necessary for your workflow 26 | android: true 27 | dotnet: true 28 | haskell: true 29 | large-packages: true 30 | docker-images: true 31 | swap-storage: true 32 | - name: Set up QEMU 33 | uses: docker/setup-qemu-action@v3 34 | - name: Set up Docker Buildx 35 | uses: docker/setup-buildx-action@v3 36 | - name: Login to Aliyun Container Registry (ACR) 37 | uses: docker/login-action@v3 38 | with: 39 | registry: registry.cn-hangzhou.aliyuncs.com 40 | # region-id: cn-hangzhou # 3 41 | username: "${{ secrets.ACR_USERNAME }}" 42 | password: "${{ secrets.ACR_PASSWORD }}" 43 | - name: Build and Push Docker Image 44 | uses: docker/build-push-action@v5 45 | with: 46 | context: . 47 | file: ./Dockerfile2 48 | platforms: linux/amd64,linux/arm64,linux/ppc64le,linux/s390x 49 | push: true 50 | tags: registry.cn-hangzhou.aliyuncs.com/moneynote/moneynote-all:latest 51 | -------------------------------------------------------------------------------- /.github/workflows/docker-image-all-no-mysql-ali.yml: -------------------------------------------------------------------------------- 1 | name: all no mysql acr image 2 | 3 | # 触发条件:在 push 到 main 分支后 4 | on: 5 | push: 6 | branches: 7 | - main1 8 | paths: 9 | - 'web2/**' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | - name: Free Disk space 18 | uses: jlumbroso/free-disk-space@main 19 | with: 20 | # this might remove tools that are actually needed, 21 | # if set to "true" but frees about 6 GB 22 | tool-cache: false 23 | 24 | # all of these default to true, but feel free to set to 25 | # "false" if necessary for your workflow 26 | android: true 27 | dotnet: true 28 | haskell: true 29 | large-packages: true 30 | docker-images: true 31 | swap-storage: true 32 | - name: Set up QEMU 33 | uses: docker/setup-qemu-action@v3 34 | - name: Set up Docker Buildx 35 | uses: docker/setup-buildx-action@v3 36 | - name: Login to Aliyun Container Registry (ACR) 37 | uses: docker/login-action@v3 38 | with: 39 | registry: registry.cn-hangzhou.aliyuncs.com 40 | # region-id: cn-hangzhou # 3 41 | username: "${{ secrets.ACR_USERNAME }}" 42 | password: "${{ secrets.ACR_PASSWORD }}" 43 | - name: Build and Push Docker Image 44 | uses: docker/build-push-action@v5 45 | with: 46 | context: . 47 | file: ./Dockerfile3 48 | platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/ppc64le,linux/s390x 49 | push: true 50 | tags: registry.cn-hangzhou.aliyuncs.com/moneynote/moneynote-all-no-mysql:latest 51 | -------------------------------------------------------------------------------- /.github/workflows/docker-image-all-no-mysql.yml: -------------------------------------------------------------------------------- 1 | name: all no mysql hub image 2 | 3 | # 触发条件:在 push 到 main 分支后 4 | on: 5 | push: 6 | branches: 7 | - main1 8 | paths: 9 | - 'web2/**' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | - name: Free Disk space 18 | uses: jlumbroso/free-disk-space@main 19 | with: 20 | # this might remove tools that are actually needed, 21 | # if set to "true" but frees about 6 GB 22 | tool-cache: false 23 | 24 | # all of these default to true, but feel free to set to 25 | # "false" if necessary for your workflow 26 | android: true 27 | dotnet: true 28 | haskell: true 29 | large-packages: true 30 | docker-images: true 31 | swap-storage: true 32 | - name: Set up QEMU 33 | uses: docker/setup-qemu-action@v3 34 | - name: Set up Docker Buildx 35 | uses: docker/setup-buildx-action@v3 36 | - name: Login to Docker Hub 37 | uses: docker/login-action@v3 38 | with: 39 | username: ${{ secrets.DOCKER_USERNAME }} 40 | password: ${{ secrets.DOCKER_PASS }} 41 | - name: Build and Push Docker Image 42 | uses: docker/build-push-action@v5 43 | with: 44 | context: . 45 | file: ./Dockerfile3 46 | platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/ppc64le,linux/s390x 47 | push: true 48 | tags: markliu2018/moneynote-all-no-mysql:latest 49 | -------------------------------------------------------------------------------- /.github/workflows/docker-image-all.yml: -------------------------------------------------------------------------------- 1 | name: all hub image 2 | 3 | # 触发条件:在 push 到 main 分支后 4 | on: 5 | push: 6 | branches: 7 | - main1 8 | paths: 9 | - 'web2/**' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | - name: Free Disk space 18 | uses: jlumbroso/free-disk-space@main 19 | with: 20 | # this might remove tools that are actually needed, 21 | # if set to "true" but frees about 6 GB 22 | tool-cache: false 23 | 24 | # all of these default to true, but feel free to set to 25 | # "false" if necessary for your workflow 26 | android: true 27 | dotnet: true 28 | haskell: true 29 | large-packages: true 30 | docker-images: true 31 | swap-storage: true 32 | - name: Set up QEMU 33 | uses: docker/setup-qemu-action@v3 34 | - name: Set up Docker Buildx 35 | uses: docker/setup-buildx-action@v3 36 | - name: Login to Docker Hub 37 | uses: docker/login-action@v3 38 | with: 39 | username: ${{ secrets.DOCKER_USERNAME }} 40 | password: ${{ secrets.DOCKER_PASS }} 41 | - name: Build and Push Docker Image 42 | uses: docker/build-push-action@v5 43 | with: 44 | context: . 45 | file: ./Dockerfile2 46 | platforms: linux/amd64,linux/arm64,linux/ppc64le,linux/s390x 47 | push: true 48 | tags: markliu2018/moneynote-all:latest 49 | -------------------------------------------------------------------------------- /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: docker hub image 2 | 3 | # 触发条件:在 push 到 main 分支后 4 | on: 5 | push: 6 | branches: 7 | - main 8 | paths: 9 | - 'web2/**' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | - name: Free Disk space 18 | uses: jlumbroso/free-disk-space@main 19 | with: 20 | # this might remove tools that are actually needed, 21 | # if set to "true" but frees about 6 GB 22 | tool-cache: false 23 | 24 | # all of these default to true, but feel free to set to 25 | # "false" if necessary for your workflow 26 | android: true 27 | dotnet: true 28 | haskell: true 29 | large-packages: true 30 | docker-images: true 31 | swap-storage: true 32 | - name: Set up QEMU 33 | uses: docker/setup-qemu-action@v3 34 | - name: Set up Docker Buildx 35 | uses: docker/setup-buildx-action@v3 36 | - name: Login to Docker Hub 37 | uses: docker/login-action@v3 38 | with: 39 | username: ${{ secrets.DOCKER_USERNAME }} 40 | password: ${{ secrets.DOCKER_PASS }} 41 | - name: Build and Push Docker Image 42 | uses: docker/build-push-action@v5 43 | with: 44 | context: . 45 | platforms: linux/amd64,linux/arm64,linux/386,linux/arm/v6 46 | push: true 47 | tags: markliu2018/moneynote-h5:latest 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Symbolication related 36 | app.*.symbols 37 | 38 | # Obfuscation related 39 | app.*.map.json 40 | 41 | # Android Studio will place build artifacts here 42 | /android/app/debug 43 | /android/app/profile 44 | /android/app/release 45 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "12fccda598477eddd19f93040a1dba24f915b9be" 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: 12fccda598477eddd19f93040a1dba24f915b9be 17 | base_revision: 12fccda598477eddd19f93040a1dba24f915b9be 18 | - platform: web 19 | create_revision: 12fccda598477eddd19f93040a1dba24f915b9be 20 | base_revision: 12fccda598477eddd19f93040a1dba24f915b9be 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 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.21-alpine 2 | COPY ./docker/nginx.conf.template /etc/nginx/templates/default.conf.template 3 | COPY ./docker/gzip.conf /etc/nginx/conf.d/gzip.conf 4 | COPY ./web2/ /usr/share/nginx/html 5 | 6 | #FROM node:16-slim as build 7 | #WORKDIR /workspace/app 8 | #COPY src src 9 | #COPY package.json . 10 | #COPY package-lock.json . 11 | #RUN npm install 12 | #RUN npm run build 13 | # 14 | #FROM nginx:1.21-alpine 15 | #COPY ./docker/nginx.conf.template /etc/nginx/templates/default.conf.template 16 | #COPY ./docker/gzip.conf /etc/nginx/conf.d/gzip.conf 17 | #COPY --from=build /workspace/app/dist/ /usr/share/nginx/html 18 | -------------------------------------------------------------------------------- /Dockerfile2: -------------------------------------------------------------------------------- 1 | FROM markliu2018/moneynote-all:latest 2 | COPY ./web2/ /var/www/h5 3 | 4 | WORKDIR /app 5 | CMD service php8.1-fpm start && service mysql start && service nginx start && java -jar app.jar 6 | 7 | EXPOSE 3306 8 | EXPOSE 80 9 | EXPOSE 9092 10 | EXPOSE 81 11 | EXPOSE 82 12 | -------------------------------------------------------------------------------- /Dockerfile3: -------------------------------------------------------------------------------- 1 | FROM markliu2018/moneynote-all-no-mysql:latest 2 | COPY ./web2/ /var/www/h5 3 | 4 | WORKDIR /app 5 | CMD service nginx start && java -jar app.jar 6 | 7 | EXPOSE 9092 8 | EXPOSE 81 9 | EXPOSE 82 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 九快记账App 2 | 3 | 本项目是九快记账App端源代码,使用Flutter开发,支持安卓和iOS端。 4 | 5 | [安卓APK下载地址](https://github.com/getmoneynote/moneynote-flutter/releases) 6 | 7 | 登录界面后台地址为运行API的地址。示例:http://www.xxx.com, http://ip:43743 8 | 9 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | def localProperties = new Properties() 8 | def localPropertiesFile = rootProject.file('local.properties') 9 | if (localPropertiesFile.exists()) { 10 | localPropertiesFile.withReader('UTF-8') { reader -> 11 | localProperties.load(reader) 12 | } 13 | } 14 | 15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 16 | if (flutterVersionCode == null) { 17 | flutterVersionCode = '1' 18 | } 19 | 20 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 21 | if (flutterVersionName == null) { 22 | flutterVersionName = '1.0' 23 | } 24 | 25 | android { 26 | namespace "cn.biq.moneynote" 27 | compileSdkVersion 34 28 | ndkVersion flutter.ndkVersion 29 | 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | 35 | defaultConfig { 36 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 37 | applicationId "cn.biq.moneynote" 38 | // You can update the following values to match your application needs. 39 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 40 | minSdkVersion localProperties.getProperty('flutter.minSdkVersion') 41 | targetSdkVersion flutter.targetSdkVersion 42 | versionCode flutterVersionCode.toInteger() 43 | versionName flutterVersionName 44 | } 45 | 46 | buildTypes { 47 | release { 48 | // TODO: Add your own signing config for the release build. 49 | // Signing with the debug keys for now, so `flutter run --release` works. 50 | signingConfig signingConfigs.debug 51 | } 52 | } 53 | } 54 | 55 | flutter { 56 | source '../..' 57 | } 58 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /android/app/src/main/java/cn/biq/moneynote/MainActivity.java: -------------------------------------------------------------------------------- 1 | package cn.biq.moneynote; 2 | 3 | import io.flutter.embedding.android.FlutterActivity; 4 | 5 | public class MainActivity extends FlutterActivity { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/android/app/src/main/res/mipmap-hdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/android/app/src/main/res/mipmap-mdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.7.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.3.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 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | tasks.register("clean", Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | } 9 | settings.ext.flutterSdkPath = flutterSdkPath() 10 | 11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") 12 | 13 | plugins { 14 | id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false 15 | } 16 | } 17 | 18 | include ":app" 19 | 20 | apply from: "${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle" 21 | -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/assets/logo.png -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | moneywhere-h5: 5 | image: nginx:1.21-alpine 6 | volumes: 7 | - ./web2:/usr/share/nginx/html 8 | ports: 9 | - "9000:80" -------------------------------------------------------------------------------- /docker/gzip.conf: -------------------------------------------------------------------------------- 1 | gzip on; 2 | gzip_min_length 1k; 3 | gzip_buffers 4 16k; 4 | gzip_http_version 1.1; 5 | gzip_comp_level 2; 6 | gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml; 7 | gzip_vary on; 8 | gzip_proxied expired no-cache no-store private auth; 9 | -------------------------------------------------------------------------------- /docker/nginx.conf.template: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | listen [::]:80; 4 | server_name localhost; 5 | 6 | client_max_body_size 50M; 7 | client_header_timeout 1m; 8 | client_body_timeout 1m; 9 | proxy_connect_timeout 60s; 10 | proxy_read_timeout 1m; 11 | proxy_send_timeout 1m; 12 | 13 | location / { 14 | root /usr/share/nginx/html; 15 | index index.html index.htm; 16 | } 17 | location ~ ^/api/v1 { 18 | proxy_pass ${USER_API_HOST}; 19 | proxy_set_header Host $host; 20 | proxy_set_header Cookie $http_cookie; 21 | proxy_set_header X-Real-IP $remote_addr; 22 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 23 | proxy_set_header X-Forwarded-Proto $scheme; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /flutter_launcher_icons.yaml: -------------------------------------------------------------------------------- 1 | dev_dependencies: 2 | flutter_launcher_icons: "^0.13.1" 3 | 4 | flutter_launcher_icons: 5 | android: "launcher_icon" 6 | ios: true 7 | image_path: "assets/logo.png" 8 | min_sdk_android: 21 # android min sdk min:16, default 21 9 | web: 10 | generate: true 11 | image_path: "assets/logo.png" 12 | background_color: "#hexcode" 13 | theme_color: "#hexcode" 14 | windows: 15 | generate: true 16 | image_path: "assets/logo.png" 17 | icon_size: 48 # min:48, max:256, default: 48 18 | macos: 19 | generate: true 20 | image_path: "assets/logo.png" -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/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/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/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/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/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/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/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/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/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/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/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/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/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 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Moneynote 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | MoneyNote 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /lib/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_easyloading/flutter_easyloading.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:flutter_localizations/flutter_localizations.dart'; 5 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 6 | import '/app/bindings/initial_binding.dart'; 7 | import '/app/core/values/app_values.dart'; 8 | import '/app/routes/app_pages.dart'; 9 | import '/generated/locales.g.dart'; 10 | import 'theme.dart'; 11 | 12 | class App extends StatelessWidget { 13 | 14 | const App({super.key}); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return GetMaterialApp( 19 | title: AppValues.appName, 20 | translationsKeys: AppTranslation.translations, 21 | locale: Get.deviceLocale, 22 | // locale: const Locale('zh', 'CN'), 23 | // locale: const Locale('en', 'US'), 24 | fallbackLocale: const Locale('en', 'US'), 25 | localizationsDelegates: const [ 26 | RefreshLocalizations.delegate, 27 | GlobalMaterialLocalizations.delegate, 28 | GlobalWidgetsLocalizations.delegate, 29 | GlobalCupertinoLocalizations.delegate, 30 | ], 31 | supportedLocales: const [ 32 | Locale('en', 'US'), 33 | Locale('zh', 'CN'), 34 | ], 35 | theme: lightTheme, 36 | darkTheme: darkTheme, 37 | themeMode: ThemeMode.system, 38 | builder: EasyLoading.init(), 39 | initialRoute: AppPages.INITIAL, 40 | initialBinding: InitialBinding(), 41 | getPages: AppPages.routes, 42 | ); 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /lib/app/bindings/initial_binding.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import '/app/modules/common/select/select_controller.dart'; 3 | import '/app/modules/my/controllers/language_controller.dart'; 4 | import '/app/modules/my/controllers/theme_controller.dart'; 5 | import '/app/modules/login/controllers/auth_controller.dart'; 6 | 7 | class InitialBinding implements Bindings { 8 | 9 | @override 10 | void dependencies() { 11 | Get.put(AuthController()); 12 | Get.put(LanguageController()); 13 | Get.put(ThemeController()); 14 | Get.put(SelectController()); 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /lib/app/core/base/base_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | 3 | abstract class BaseController extends GetxController { 4 | 5 | } -------------------------------------------------------------------------------- /lib/app/core/base/base_repository.dart: -------------------------------------------------------------------------------- 1 | import '../../network/http.dart'; 2 | 3 | class BaseRepository { 4 | 5 | static Future>> query(String prefix, Map query) async { 6 | List data = (await Http.get(prefix, params: { 7 | ...query, 8 | }))['data']; 9 | return List>.from(data); 10 | } 11 | 12 | static Future>> query1(String prefix, Map query) async { 13 | List data = (await Http.get(prefix, params: { 14 | ...query, 15 | ...{ 16 | 'enable': true, 17 | } 18 | }))['data']; 19 | return List>.from(data); 20 | } 21 | 22 | static Future toggle(String prefix, int id) async { 23 | return (await Http.patch('$prefix/$id/toggle'))['success']; 24 | } 25 | 26 | static Future delete(String prefix, int id) async { 27 | return (await Http.delete('$prefix/$id'))['success']; 28 | } 29 | 30 | static Future> get(String prefix, int id) async { 31 | return (await Http.get('$prefix/$id'))['data']; 32 | } 33 | 34 | static Future>> queryAll(String prefix, {Map? params}) async { 35 | List data = (await Http.get('$prefix/all', params: params))['data']; 36 | return List>.from(data); 37 | } 38 | 39 | static Future add(String prefix, Map form) async { 40 | return (await Http.post(prefix, data: form))['success']; 41 | } 42 | 43 | static Future update(String prefix, int id, Map form) async { 44 | return (await Http.put('$prefix/$id', data: form))['success']; 45 | } 46 | 47 | static Future> update2(String prefix, int id, Map form) async { 48 | return (await Http.put('$prefix/$id', data: form)); 49 | } 50 | 51 | static Future action(String uri) async { 52 | return (await Http.patch(uri))['success']; 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /lib/app/core/base/enums.dart: -------------------------------------------------------------------------------- 1 | enum LoadDataStatus { initial, progress, success, empty, failure } -------------------------------------------------------------------------------- /lib/app/core/commons/form/not_empty_formz.dart: -------------------------------------------------------------------------------- 1 | import 'package:formz/formz.dart'; 2 | 3 | enum NotEmptyError { empty } 4 | 5 | class NotEmptyFormz extends FormzInput { 6 | 7 | const NotEmptyFormz.pure() : super.pure(''); 8 | const NotEmptyFormz.dirty({String value = ''}) : super.dirty(value); 9 | 10 | @override 11 | NotEmptyError? validator(String? value) { 12 | // if (isPure) return null; 13 | // return value.isEmpty ? NameError.emp null; 14 | return (value?.isNotEmpty ?? false) ? null : NotEmptyError.empty; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /lib/app/core/commons/form/not_empty_num_formz.dart: -------------------------------------------------------------------------------- 1 | import 'package:formz/formz.dart'; 2 | 3 | enum NotEmptyNumError { empty, invalid } 4 | 5 | class NotEmptyNumFormz extends FormzInput { 6 | 7 | const NotEmptyNumFormz.pure() : super.pure(''); 8 | const NotEmptyNumFormz.dirty({String value = ''}) : super.dirty(value); 9 | 10 | static final reg = RegExp(r'^-?\d{1,9}(\.\d{0,2})?$',); 11 | 12 | @override 13 | NotEmptyNumError? validator(String? value) { 14 | // if (isPure) return null; 15 | // return value.isEmpty ? NotEmptyNumError.empty : null; 16 | if (value?.isNotEmpty ?? false) { 17 | return reg.hasMatch(value!) ? null : NotEmptyNumError.invalid; 18 | } else { 19 | return NotEmptyNumError.empty; 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /lib/app/core/commons/widget_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | void fullDialog(BuildContext context, Widget widget) { 4 | Navigator.of(context, rootNavigator: false).push( // ensures fullscreen 5 | CupertinoPageRoute( 6 | fullscreenDialog: true, 7 | builder: (context) => widget 8 | ) 9 | ); 10 | } -------------------------------------------------------------------------------- /lib/app/core/components/asterisk.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Asterisk extends StatelessWidget { 4 | 5 | const Asterisk({super.key}); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return const Text('*', style: TextStyle(color: Colors.red)); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /lib/app/core/components/bottomsheet_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class BottomSheetContainer extends StatelessWidget { 4 | 5 | final Widget child; 6 | 7 | const BottomSheetContainer({ 8 | super.key, 9 | required this.child 10 | }); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Container( 15 | decoration: BoxDecoration( 16 | color: Theme.of(context).cardColor, 17 | borderRadius: const BorderRadius.only( 18 | topLeft: Radius.circular(10), 19 | topRight: Radius.circular(10), 20 | ), 21 | ), 22 | child: child, 23 | ); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /lib/app/core/components/detail_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class DetailItem extends StatelessWidget { 4 | 5 | const DetailItem({ 6 | super.key, 7 | required this.label, 8 | this.value, 9 | this.space = true, 10 | this.tail, 11 | this.crossAlign = CrossAxisAlignment.center, 12 | }); 13 | 14 | final String label; 15 | final String? value; 16 | final bool space; 17 | final Widget? tail; 18 | final CrossAxisAlignment crossAlign; 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | TextStyle? style1 = Theme.of(context).textTheme.bodyLarge; 23 | TextStyle? style2 = Theme.of(context).textTheme.bodyLarge; 24 | final Widget row = Row( 25 | mainAxisAlignment: MainAxisAlignment.start, 26 | crossAxisAlignment: crossAlign, 27 | children: [ 28 | Text(label, style: style1), 29 | const Text(': '), 30 | Flexible(child: Text(value ?? '', style: style2)), 31 | const SizedBox(width: 10), 32 | if (tail != null) tail!, 33 | ] 34 | ); 35 | if (space) { 36 | return Padding( 37 | padding: const EdgeInsets.only(bottom: 15), 38 | child: row, 39 | ); 40 | } else { 41 | return row; 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /lib/app/core/components/dialog_confirm.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/generated/locales.g.dart'; 4 | 5 | class DialogConfirm extends StatelessWidget { 6 | 7 | final String? title; 8 | final String? content; 9 | final String? okText; 10 | final String? cancelText; 11 | final Widget child; 12 | final Function() onConfirm; 13 | final bool enable; 14 | 15 | const DialogConfirm({ 16 | super.key, 17 | this.title, 18 | this.content, 19 | this.okText, 20 | this.cancelText, 21 | required this.child, 22 | required this.onConfirm, 23 | this.enable = true, 24 | }); 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return GestureDetector( 29 | onTap: enable ? () async { 30 | final bool? isConfirm = await showDialog( 31 | context: context, 32 | builder: (_) => WillPopScope( 33 | child: AlertDialog( 34 | title: title != null ? Text(title!) : null, 35 | content: Text( content ?? LocaleKeys.common_confirmDialogTitle.tr ), 36 | actions: [ 37 | TextButton( 38 | child: Text( cancelText ?? LocaleKeys.common_cancel.tr ), 39 | onPressed: () => Navigator.pop(context, false), 40 | ), 41 | TextButton( 42 | child: Text( okText ?? LocaleKeys.common_ok.tr ), 43 | onPressed: () => Navigator.pop(context, true), 44 | ), 45 | ], 46 | ), 47 | onWillPop: () async { 48 | Navigator.pop(context, false); 49 | return true; 50 | }, 51 | ), 52 | ); 53 | if (isConfirm ?? false) { 54 | onConfirm(); 55 | } 56 | } : null, 57 | child: child, 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lib/app/core/components/flutter_tree-2.0.3/flutter_tree.dart: -------------------------------------------------------------------------------- 1 | library flutter_tree; 2 | 3 | export 'src/tree_view.dart'; 4 | export 'src/tree_node_data.dart'; 5 | -------------------------------------------------------------------------------- /lib/app/core/components/flutter_tree-2.0.3/src/tree_node_data.dart: -------------------------------------------------------------------------------- 1 | class TreeNodeData { 2 | String title; 3 | bool expaned; 4 | bool checked; 5 | dynamic extra; 6 | List children; 7 | 8 | TreeNodeData({ 9 | required this.title, 10 | required this.expaned, 11 | required this.checked, 12 | required this.children, 13 | this.extra, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /lib/app/core/components/form/my_form_switch.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../values/app_text_styles.dart'; 4 | import '../asterisk.dart'; 5 | 6 | class MyFormSwitch extends StatelessWidget { 7 | 8 | final String label; 9 | final bool? value; 10 | final Function(bool value) onChange; 11 | final bool required; 12 | final bool readOnly; 13 | 14 | const MyFormSwitch({ 15 | super.key, 16 | required this.label, 17 | this.value = true, 18 | required this.onChange, 19 | this.required = false, 20 | this.readOnly = false, 21 | }); 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return Row( 26 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 27 | crossAxisAlignment: CrossAxisAlignment.center, 28 | children: [ 29 | if (required) const Asterisk(), 30 | Text(label, style: readOnly ? AppTextStyle.formReadOnlyLabelStyle : AppTextStyle.formLabelStyle), 31 | const SizedBox(width: 20), 32 | Expanded( 33 | child: Align( 34 | alignment: Alignment.centerLeft, 35 | child: Switch( 36 | value: value ?? true, 37 | onChanged: readOnly ? null : (value) => onChange(value), 38 | ), 39 | ) 40 | ) 41 | ] 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/app/core/components/form/my_form_text.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '/app/core/values/app_text_styles.dart'; 3 | 4 | import '../../utils/utils.dart'; 5 | import '../asterisk.dart'; 6 | 7 | class MyFormText extends StatefulWidget { 8 | 9 | final String label; 10 | final dynamic value; 11 | final Function(String value) onChange; 12 | final String? errorText; 13 | final bool required; 14 | final bool readOnly; 15 | 16 | const MyFormText({ 17 | super.key, 18 | required this.label, 19 | this.value, 20 | required this.onChange, 21 | this.errorText, 22 | this.required = false, 23 | this.readOnly = false, 24 | }); 25 | 26 | @override 27 | State createState() => _MyFormTextState(); 28 | } 29 | 30 | class _MyFormTextState extends State { 31 | 32 | late TextEditingController controller; 33 | 34 | @override 35 | void initState() { 36 | super.initState(); 37 | controller = TextEditingController(); 38 | } 39 | 40 | @override 41 | void dispose() { 42 | super.dispose(); 43 | controller.dispose(); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | String value = ''; 49 | if (widget.value.runtimeType == double) { 50 | value = removeDecimalZero(widget.value); 51 | } else if (widget.value.runtimeType == String) { 52 | value = widget.value; 53 | } else if (widget.value.runtimeType == Null) { 54 | value = ''; 55 | } else { 56 | value = widget.value.toString(); 57 | } 58 | controller.value = controller.value.copyWith(text: value); 59 | return Row( 60 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 61 | crossAxisAlignment: CrossAxisAlignment.center, 62 | children: [ 63 | if (widget.required) const Asterisk(), 64 | Text(widget.label, style: widget.readOnly ? AppTextStyle.formReadOnlyLabelStyle : AppTextStyle.formLabelStyle), 65 | const SizedBox(width: 15), 66 | Expanded( 67 | child: Stack( 68 | alignment: Alignment.center, 69 | clipBehavior: Clip.none, 70 | children: [ 71 | TextField( 72 | controller: controller, 73 | onChanged: (value) => widget.onChange(value), 74 | enabled: !widget.readOnly, 75 | style: widget.readOnly ? AppTextStyle.formReadOnlyValueStyle : AppTextStyle.formValueStyle, 76 | maxLines: null, 77 | keyboardType: TextInputType.multiline, 78 | decoration: AppTextStyle.inputDecoration, 79 | ), 80 | Positioned( 81 | bottom: -17, 82 | left: 0, 83 | child: Text(widget.errorText ?? '', style: const TextStyle(fontSize: 12, color: Colors.red)), 84 | ) 85 | ], 86 | ) 87 | ) 88 | ] 89 | ); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /lib/app/core/components/form/my_select.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/app/core/values/app_text_styles.dart'; 4 | import '/generated/locales.g.dart'; 5 | import '../../utils/utils.dart'; 6 | import '../asterisk.dart'; 7 | 8 | class MySelect extends StatelessWidget { 9 | 10 | final String label; 11 | final dynamic value; 12 | final bool readOnly; 13 | final bool required; 14 | final bool allowClear; 15 | final Function? onClear; 16 | final Function? onFocus; 17 | final bool multiple; 18 | 19 | const MySelect({ 20 | super.key, 21 | required this.label, 22 | this.value, 23 | this.readOnly = false, 24 | this.required = false, 25 | this.allowClear = false, 26 | this.onClear, 27 | this.onFocus, 28 | this.multiple = false, 29 | }); 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return ListTile( 34 | contentPadding: const EdgeInsets.only(left: 0.0, right: 0.0), 35 | onTap: readOnly ? null : () { 36 | if (onFocus != null) { 37 | onFocus!.call(); 38 | } 39 | }, 40 | title: Row( 41 | mainAxisAlignment: MainAxisAlignment.start, 42 | crossAxisAlignment: CrossAxisAlignment.center, 43 | children: [ 44 | if (required) const Asterisk(), 45 | Text( 46 | label, 47 | maxLines: 1, 48 | softWrap: true, 49 | overflow: TextOverflow.ellipsis, 50 | style: readOnly ? AppTextStyle.formReadOnlyLabelStyle : AppTextStyle.formLabelStyle, 51 | ), 52 | ], 53 | ), 54 | trailing: Row( 55 | mainAxisSize: MainAxisSize.min, 56 | children: [ 57 | Flexible( 58 | child: Text( 59 | labelText(), 60 | style: readOnly ? AppTextStyle.formReadOnlyLabelStyle : AppTextStyle.formLabelStyle, 61 | softWrap: true, 62 | ) 63 | ), 64 | const Icon(Icons.keyboard_arrow_right), 65 | if (allowClear && !isNullEmpty(value)) 66 | IconButton( 67 | onPressed: () { 68 | onClear?.call(); 69 | }, 70 | icon: const Icon(Icons.backspace) 71 | ) 72 | ], 73 | ), 74 | ); 75 | } 76 | 77 | String labelText() { 78 | if (multiple) { 79 | if (value?.isEmpty ?? true) { 80 | return LocaleKeys.form_selectPlaceholder1.tr; 81 | } else { 82 | return value.map((e) => e['label']).toList().join(', '); 83 | } 84 | 85 | } else { 86 | return value?['label'] ?? LocaleKeys.form_selectPlaceholder2.tr; 87 | } 88 | } 89 | 90 | } -------------------------------------------------------------------------------- /lib/app/core/components/form/my_tree_option.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/app/core/components/flutter_tree-2.0.3/flutter_tree.dart'; 4 | import '../../base/enums.dart'; 5 | import '../pages/index.dart'; 6 | 7 | 8 | class MyTreeOption extends StatefulWidget { 9 | 10 | final String pageTitle; 11 | final List options; 12 | final LoadDataStatus status; 13 | final List? values; 14 | final Function onSelect; 15 | 16 | const MyTreeOption({ 17 | super.key, 18 | required this.pageTitle, 19 | this.status = LoadDataStatus.initial, 20 | this.options = const [], 21 | this.values = const [], 22 | required this.onSelect, 23 | }); 24 | 25 | @override 26 | State createState() => _MyTreeOptionState(); 27 | 28 | } 29 | 30 | class _MyTreeOptionState extends State { 31 | 32 | late List> newValues; 33 | 34 | @override 35 | void initState() { 36 | super.initState(); 37 | newValues = [...(widget.values ?? [])]; 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | return WillPopScope( 43 | onWillPop: () async { 44 | widget.onSelect.call(newValues); 45 | return true; 46 | }, 47 | child: Scaffold( 48 | appBar: AppBar( 49 | centerTitle: true, 50 | title: Text(widget.pageTitle), 51 | actions: [ 52 | IconButton( 53 | icon: const Icon(Icons.close), 54 | onPressed: () { 55 | Get.back(); 56 | }, 57 | ), 58 | ], 59 | ), 60 | body: () { 61 | switch (widget.status) { 62 | case LoadDataStatus.progress: 63 | case LoadDataStatus.initial: 64 | return const LoadingPage(); 65 | case LoadDataStatus.success: 66 | TreeNodeData mapServerDataToTreeData(Map data) { 67 | return TreeNodeData( 68 | extra: data, 69 | title: data['label'], 70 | expaned: false, 71 | checked: newValues.map((e) => e['value']).contains(data['value']), 72 | children: List.from(data['children']?.map((x) => mapServerDataToTreeData(x)) ?? []), 73 | ); 74 | } 75 | 76 | List treeData = List.generate( 77 | widget.options.length, 78 | (index) => mapServerDataToTreeData(widget.options[index]), 79 | ); 80 | 81 | return TreeView( 82 | data: treeData, 83 | showFilter: false, 84 | showCheckBox: true, 85 | onCheck: (checked, data) { 86 | if (checked) { 87 | setState(() { 88 | newValues.add(data.extra); 89 | }); 90 | } else { 91 | setState(() { 92 | newValues.removeWhere((e) => e['value'] == data.extra['value']); 93 | }); 94 | } 95 | }, 96 | ); 97 | 98 | case LoadDataStatus.empty: 99 | return const EmptyPage(); 100 | case LoadDataStatus.failure: 101 | return const ErrorPage(); 102 | } 103 | }(), 104 | ), 105 | ); 106 | } 107 | 108 | 109 | 110 | } -------------------------------------------------------------------------------- /lib/app/core/components/lazy_indexed_stack.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | class LazyIndexedStack extends StatefulWidget { 4 | 5 | final AlignmentGeometry alignment; 6 | final TextDirection? textDirection; 7 | final StackFit sizing; 8 | final int? index; 9 | 10 | //reuse the created view 11 | final bool reuse; 12 | 13 | final int itemCount; 14 | final IndexedWidgetBuilder itemBuilder; 15 | 16 | const LazyIndexedStack({ 17 | super.key, 18 | this.alignment = AlignmentDirectional.topStart, 19 | this.textDirection, 20 | this.sizing = StackFit.loose, 21 | this.index, 22 | this.reuse = true, 23 | required this.itemBuilder, 24 | this.itemCount = 0 25 | }); 26 | 27 | @override 28 | State createState() => _LazyIndexedStackState(); 29 | 30 | } 31 | 32 | class _LazyIndexedStackState extends State { 33 | 34 | late List _children; 35 | late List _loaded; 36 | 37 | @override 38 | void initState() { 39 | _loaded = []; 40 | _children = []; 41 | for (int i = 0; i < widget.itemCount; ++i) { 42 | if (i == widget.index) { 43 | _children.add(widget.itemBuilder(context, i)); 44 | _loaded.add(true); 45 | } else { 46 | _children.add(Container()); 47 | _loaded.add(false); 48 | } 49 | } 50 | super.initState(); 51 | } 52 | 53 | @override 54 | void didUpdateWidget(LazyIndexedStack oldWidget) { 55 | for (int i = 0; i < widget.itemCount; ++i) { 56 | if (i == widget.index) { 57 | if (!_loaded[i]) { 58 | _children[i] = widget.itemBuilder(context, i); 59 | _loaded[i] = true; 60 | } else { 61 | if (widget.reuse) { 62 | return; 63 | } 64 | _children[i] = widget.itemBuilder(context, i); 65 | } 66 | } 67 | } 68 | 69 | super.didUpdateWidget(oldWidget); 70 | } 71 | 72 | @override 73 | Widget build(BuildContext context) { 74 | return IndexedStack( 75 | index: widget.index, 76 | alignment: widget.alignment, 77 | textDirection: widget.textDirection, 78 | sizing: widget.sizing, 79 | children: _children, 80 | ); 81 | } 82 | } -------------------------------------------------------------------------------- /lib/app/core/components/loading_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class LoadingIcon extends StatelessWidget { 4 | 5 | const LoadingIcon({super.key}); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return const SizedBox( 10 | width: 50, 11 | child: Center( 12 | child: SizedBox( 13 | width: 16, 14 | height: 16, 15 | child: CircularProgressIndicator( 16 | valueColor: AlwaysStoppedAnimation(Colors.white), 17 | strokeWidth: 1.5, 18 | )), 19 | ), 20 | ); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /lib/app/core/components/my_form_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class MyFormPage extends StatelessWidget { 4 | 5 | final Widget title; 6 | final List children; 7 | final List actions; 8 | 9 | const MyFormPage({ 10 | super.key, 11 | required this.title, 12 | required this.children, 13 | required this.actions, 14 | }); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | 19 | return Scaffold( 20 | appBar: AppBar( 21 | centerTitle: true, 22 | title: title, 23 | actions: actions, 24 | ), 25 | body: SingleChildScrollView( 26 | child: Padding( 27 | padding: const EdgeInsets.fromLTRB(15, 0, 15, 30), 28 | child: Wrap( 29 | runSpacing: 5, 30 | children: children, 31 | ), 32 | ), 33 | ), 34 | ); 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /lib/app/core/components/order_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'popup_menu.dart'; 3 | 4 | 5 | class OrderButton extends StatelessWidget { 6 | 7 | const OrderButton({ 8 | super.key, 9 | required this.items, 10 | required this.onSelected, 11 | required this.selected, 12 | }); 13 | 14 | final Map items; 15 | final Function onSelected; 16 | final String selected; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return PopupMenu( 21 | onSelected: (selected) { 22 | onSelected.call(selected); 23 | }, 24 | items: items, 25 | selected: selected, 26 | ); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /lib/app/core/components/pages/content_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ContentPage extends StatelessWidget { 4 | 5 | const ContentPage({ 6 | super.key, 7 | required this.child, 8 | }); 9 | 10 | final Widget child; 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return SingleChildScrollView( 15 | child: Padding( 16 | padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 20), 17 | child: child, 18 | ) 19 | ); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /lib/app/core/components/pages/empty_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class EmptyPage extends StatelessWidget { 4 | 5 | const EmptyPage({super.key}); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | final theme = Theme.of(context); 10 | return Center( 11 | child: Column( 12 | mainAxisAlignment: MainAxisAlignment.center, 13 | crossAxisAlignment: CrossAxisAlignment.center, 14 | children: [ 15 | const Icon(Icons.feedback_outlined, size: 70), 16 | Text('无数据', style: theme.textTheme.headlineMedium), 17 | ], 18 | ), 19 | ); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /lib/app/core/components/pages/error_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ErrorPage extends StatelessWidget { 4 | 5 | final String? msg; 6 | final Function()? onTap; 7 | 8 | const ErrorPage({ 9 | super.key, 10 | this.msg, 11 | this.onTap, 12 | }); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | final theme = Theme.of(context); 17 | return GestureDetector( 18 | onTap: onTap, 19 | child: Container( 20 | color: Colors.white, 21 | child: Center( 22 | child: Column( 23 | mainAxisSize: MainAxisSize.min, 24 | crossAxisAlignment: CrossAxisAlignment.center, 25 | children: [ 26 | const Text('🙈', style: TextStyle(fontSize: 42)), 27 | Text( 28 | msg ?? (onTap != null ? '加载异常,点击屏幕重新加载。' : '加载异常'), 29 | style: theme.textTheme.titleLarge, 30 | ), 31 | ], 32 | ), 33 | ), 34 | ), 35 | ); 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /lib/app/core/components/pages/index.dart: -------------------------------------------------------------------------------- 1 | export 'content_page.dart'; 2 | export 'empty_page.dart'; 3 | export 'error_page.dart'; 4 | export 'loading_page.dart'; -------------------------------------------------------------------------------- /lib/app/core/components/pages/loading_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class LoadingPage extends StatelessWidget { 4 | 5 | const LoadingPage({super.key}); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return const Center( 10 | child: CircularProgressIndicator(), 11 | ); 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /lib/app/core/components/popup_menu.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class PopupMenu extends StatelessWidget { 4 | 5 | final Function(String) onSelected; 6 | final String selected; 7 | final Map items; 8 | 9 | const PopupMenu({ 10 | super.key, 11 | required this.onSelected, 12 | required this.selected, 13 | required this.items, 14 | }); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | final defaultStyle = Theme.of(context).textTheme.bodyText2; 19 | final activeStyle = defaultStyle!.copyWith(color: Theme.of(context).colorScheme.secondary); 20 | return PopupMenuButton( 21 | onSelected: onSelected, 22 | icon: const Icon(Icons.swap_vert), 23 | itemBuilder: (context) => items.entries.map((i) => PopupMenuItem( 24 | value: i.key, 25 | child: Text(i.value, style: selected == i.key ? activeStyle : defaultStyle), 26 | )).toList() 27 | ); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /lib/app/core/components/web_view_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:webview_flutter/webview_flutter.dart'; 3 | 4 | class WebViewPage extends StatefulWidget { 5 | 6 | final String title; 7 | final String url; 8 | 9 | const WebViewPage({ 10 | super.key, 11 | required this.title, 12 | required this.url 13 | }); 14 | 15 | @override 16 | State createState() => _WebViewPageState(); 17 | 18 | } 19 | 20 | class _WebViewPageState extends State { 21 | 22 | late final WebViewController controller; 23 | int loadingPercentage = 0; 24 | 25 | @override 26 | void initState() { 27 | super.initState(); 28 | controller = WebViewController() 29 | ..setJavaScriptMode(JavaScriptMode.unrestricted) 30 | ..setBackgroundColor(const Color(0x00000000)) 31 | ..setNavigationDelegate( 32 | NavigationDelegate( 33 | onProgress: (int progress) { 34 | setState(() { 35 | loadingPercentage = progress; 36 | }); 37 | }, 38 | onPageStarted: (String url) { 39 | setState(() { 40 | loadingPercentage = 0; 41 | }); 42 | }, 43 | onPageFinished: (String url) { 44 | setState(() { 45 | loadingPercentage = 100; 46 | }); 47 | }, 48 | ), 49 | ) 50 | ..loadRequest(Uri.parse(widget.url)); 51 | } 52 | 53 | @override 54 | Widget build(BuildContext context) { 55 | return Scaffold( 56 | appBar: AppBar( 57 | centerTitle: true, 58 | title: Text(widget.title), 59 | ), 60 | body: Stack( 61 | children: [ 62 | WebViewWidget(controller: controller), 63 | if (loadingPercentage < 100) LinearProgressIndicator(value: loadingPercentage / 100.0), 64 | ], 65 | ), 66 | ); 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /lib/app/core/utils/api_url.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | import '/app/core/values/app_values.dart'; 3 | import '/app/network/http_client.dart'; 4 | 5 | class ApiUrl { 6 | 7 | static Future save(String url) async { 8 | AppValues.apiUrl = url; 9 | HttpClient().init(); 10 | final SharedPreferences prefs = await SharedPreferences.getInstance(); 11 | return prefs.setString('apiUrl', url); 12 | } 13 | 14 | static Future get() async { 15 | final SharedPreferences prefs = await SharedPreferences.getInstance(); 16 | final String apiUrl = prefs.getString('apiUrl') ?? ''; 17 | if (apiUrl.isNotEmpty) { 18 | AppValues.apiUrl = apiUrl; 19 | } 20 | return apiUrl; 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /lib/app/core/utils/language.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | 3 | class Language { 4 | 5 | static Future save(String lang) async { 6 | final SharedPreferences prefs = await SharedPreferences.getInstance(); 7 | return await prefs.setString('lang', lang); 8 | } 9 | 10 | static Future get() async { 11 | final SharedPreferences prefs = await SharedPreferences.getInstance(); 12 | return prefs.getString('lang') ?? ''; 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /lib/app/core/utils/message.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_easyloading/flutter_easyloading.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:moneynote/generated/locales.g.dart'; 4 | 5 | class Message { 6 | 7 | static success(String msg) { 8 | EasyLoading.showSuccess(msg, duration: const Duration(seconds: 2)); 9 | } 10 | 11 | static error(String msg) { 12 | EasyLoading.showError(msg, duration: const Duration(seconds: 2)); 13 | } 14 | 15 | static showLoading({String? msg}) { 16 | EasyLoading.show(status: msg ?? LocaleKeys.common_loading.tr, maskType: EasyLoadingMaskType.black); 17 | } 18 | 19 | static disLoading() { 20 | EasyLoading.dismiss(); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /lib/app/core/utils/theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | 3 | class Theme { 4 | 5 | static Future save(String theme) async { 6 | final SharedPreferences prefs = await SharedPreferences.getInstance(); 7 | return await prefs.setString('theme', theme); 8 | } 9 | 10 | static Future get() async { 11 | final SharedPreferences prefs = await SharedPreferences.getInstance(); 12 | return prefs.getString('theme') ?? ''; 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /lib/app/core/utils/token.dart: -------------------------------------------------------------------------------- 1 | import 'package:shared_preferences/shared_preferences.dart'; 2 | 3 | class Token { 4 | 5 | static Future save(String token) async { 6 | final SharedPreferences prefs = await SharedPreferences.getInstance(); 7 | return await prefs.setString('accessToken', token); 8 | } 9 | 10 | static Future get() async { 11 | final SharedPreferences prefs = await SharedPreferences.getInstance(); 12 | return prefs.getString('accessToken') ?? ''; 13 | } 14 | 15 | static Future delete() async { 16 | final SharedPreferences prefs = await SharedPreferences.getInstance(); 17 | return await prefs.remove('accessToken'); 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /lib/app/core/utils/widget_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:syncfusion_flutter_charts/charts.dart'; 4 | 5 | List, String>> getDefaultDoughnutSeries(xys) { 6 | return [ 7 | DoughnutSeries, String>( 8 | radius: '80%', 9 | innerRadius: '70%', 10 | dataSource: xys, 11 | xValueMapper: (data, _) => data['x'], 12 | yValueMapper: (data, _) => data['y'], 13 | dataLabelMapper: (data, _) => "${data['x']} : ${data['percent']}%", 14 | dataLabelSettings: const DataLabelSettings( 15 | isVisible: true, 16 | textStyle: TextStyle(fontSize: 10), 17 | labelPosition: ChartDataLabelPosition.outside 18 | ), 19 | legendIconType: LegendIconType.circle, 20 | ) 21 | ]; 22 | } 23 | 24 | num calChartTotal(List> data) { 25 | num total = data.fold(0, (previousValue, element) => previousValue + element['y']*100); 26 | // 解决精度问题 27 | return total / 100; 28 | } -------------------------------------------------------------------------------- /lib/app/core/values/app_const.dart: -------------------------------------------------------------------------------- 1 | class AppConst { 2 | 3 | static const int defaultPageSize = 20; 4 | static const int pageStart = 1; // one-indexed-parameters 5 | static const String pageParameter = 'current'; 6 | static const String sortParameter = 'sort'; 7 | static const String pageSizeParameter = 'pageSize'; 8 | 9 | } -------------------------------------------------------------------------------- /lib/app/core/values/app_text_styles.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | abstract class AppTextStyle { 4 | 5 | static const loginTitle = TextStyle( 6 | fontSize: 30, 7 | fontWeight: FontWeight.w500, 8 | ); 9 | 10 | static const accountTab = TextStyle( 11 | fontSize: 15, 12 | fontWeight: FontWeight.w400, 13 | ); 14 | 15 | static const newFlow = TextStyle( 16 | fontSize: 20, 17 | fontWeight: FontWeight.w500, 18 | ); 19 | 20 | static const accountBalance = TextStyle( 21 | fontSize: 18, 22 | fontWeight: FontWeight.w500, 23 | ); 24 | 25 | static const formLabelStyle = TextStyle( 26 | fontSize: 14, 27 | ); 28 | 29 | static const formReadOnlyLabelStyle = TextStyle( 30 | fontSize: 14, 31 | color: Colors.grey 32 | ); 33 | 34 | static const formValueStyle = TextStyle( 35 | fontSize: 14, 36 | ); 37 | 38 | static const formReadOnlyValueStyle = TextStyle( 39 | fontSize: 14, 40 | color: Colors.grey 41 | ); 42 | 43 | static const optionStyle = TextStyle(color: Colors.white, fontSize: 18); 44 | 45 | static const InputDecoration inputDecoration = InputDecoration( 46 | enabledBorder: UnderlineInputBorder( 47 | borderSide: BorderSide(width: 1, color: Colors.black) 48 | ), 49 | disabledBorder: UnderlineInputBorder( 50 | borderSide: BorderSide(width: 1, color: Colors.grey) 51 | ), 52 | focusedBorder: UnderlineInputBorder( 53 | borderSide: BorderSide(width: 1, color: Colors.blue) 54 | ), 55 | ); 56 | 57 | } 58 | -------------------------------------------------------------------------------- /lib/app/core/values/app_values.dart: -------------------------------------------------------------------------------- 1 | class AppValues { 2 | 3 | static const String appName = 'MoneyNote'; 4 | static const String version = '1.0.44'; 5 | static String apiUrl = ''; 6 | 7 | } -------------------------------------------------------------------------------- /lib/app/modules/accounts/controllers/account_adjust_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:formz/formz.dart'; 3 | import '/app/modules/flows/controllers/flow_detail_controller.dart'; 4 | import '../../../core/utils/message.dart'; 5 | import '/app/modules/accounts/controllers/account_detail_controller.dart'; 6 | import '/app/modules/accounts/controllers/accounts_controller.dart'; 7 | import '/app/core/commons/form/not_empty_num_formz.dart'; 8 | import '/app/core/base/base_controller.dart'; 9 | import '../../login/controllers/auth_controller.dart'; 10 | import '../data/account_repository.dart'; 11 | 12 | 13 | class AccountAdjustController extends BaseController { 14 | 15 | bool valid = false; 16 | Map form = { }; 17 | NotEmptyNumFormz balanceFormz = const NotEmptyNumFormz.pure(); 18 | 19 | int action; 20 | Map currentRow; 21 | 22 | AccountAdjustController(this.action, this.currentRow); 23 | 24 | @override 25 | void onInit() { 26 | super.onInit(); 27 | if (action == 1) { 28 | form['createTime'] = DateTime.now().millisecondsSinceEpoch; 29 | form['book'] = Get.find().initState['book']; 30 | } else { 31 | valid = true; 32 | form['book'] = currentRow['book']; 33 | form['title'] = currentRow['title']; 34 | form['createTime'] = currentRow['createTime']; 35 | form['notes'] = currentRow['notes']; 36 | } 37 | 38 | } 39 | 40 | void balanceChanged(String value) { 41 | balanceFormz = NotEmptyNumFormz.dirty(value: value); 42 | valid = Formz.validate([balanceFormz]); 43 | form['balance'] = value; 44 | update(); 45 | } 46 | 47 | void submit() async { 48 | if (valid) { 49 | try { 50 | Message.showLoading(); 51 | bool result = false; 52 | Map newForm = { ...form }; 53 | if (form['book']?['value'] != null) { 54 | newForm['book'] = form['book']?['value']; 55 | } 56 | switch (action) { 57 | case 1: 58 | result = await AccountRepository.createAdjust(currentRow['id'], newForm); 59 | break; 60 | case 2: 61 | result = await AccountRepository.updateAdjust(currentRow['id'], newForm); 62 | break; 63 | } 64 | if (result) { 65 | Get.back(); 66 | if (action == 1) { 67 | // 更新成功要刷新列表页和详情页 68 | Get.find().reload(); 69 | Get.find().load(); 70 | } else { 71 | Get.find().load(); 72 | } 73 | } 74 | } catch (_) { 75 | _.printError(); 76 | } 77 | } 78 | } 79 | 80 | 81 | } -------------------------------------------------------------------------------- /lib/app/modules/accounts/controllers/account_detail_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import '../../../core/utils/message.dart'; 3 | import '/app/modules/accounts/controllers/accounts_controller.dart'; 4 | import '/app/core/base/base_repository.dart'; 5 | import '/app/core/base/enums.dart'; 6 | import '/app/core/base/base_controller.dart'; 7 | 8 | class AccountDetailController extends BaseController { 9 | 10 | int id; 11 | LoadDataStatus status = LoadDataStatus.initial; 12 | Map item = {}; 13 | LoadDataStatus deleteStatus = LoadDataStatus.initial; 14 | 15 | AccountDetailController(this.id); 16 | 17 | @override 18 | void onInit() { 19 | super.onInit(); 20 | load(); 21 | } 22 | 23 | void load() async { 24 | try { 25 | status = LoadDataStatus.progress; 26 | update(); 27 | item = await BaseRepository.get('accounts', id); 28 | status = LoadDataStatus.success; 29 | update(); 30 | } catch (_) { 31 | status = LoadDataStatus.failure; 32 | update(); 33 | } 34 | } 35 | 36 | void delete() async { 37 | try { 38 | Message.showLoading(); 39 | final result = await BaseRepository.toggle('accounts', id); 40 | if (result) { 41 | Get.back(); 42 | Get.find().reload(); 43 | } 44 | } catch (_) { 45 | _.printError(); 46 | } 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /lib/app/modules/accounts/controllers/account_form_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:formz/formz.dart'; 2 | import 'package:get/get.dart'; 3 | import '/app/core/utils/message.dart'; 4 | import '/app/core/base/base_repository.dart'; 5 | import '/app/core/utils/utils.dart'; 6 | import '/app/core/commons/form/not_empty_formz.dart'; 7 | import '/app/core/commons/form/not_empty_num_formz.dart'; 8 | import '../../login/controllers/auth_controller.dart'; 9 | import '/app/core/base/base_controller.dart'; 10 | import 'account_detail_controller.dart'; 11 | import 'accounts_controller.dart'; 12 | 13 | class AccountFormController extends BaseController { 14 | 15 | bool valid = false; 16 | Map form = { }; 17 | NotEmptyFormz nameFormz = const NotEmptyFormz.pure(); 18 | NotEmptyNumFormz balanceFormz = const NotEmptyNumFormz.pure(); 19 | 20 | String type; 21 | int action; 22 | Map currentRow; 23 | 24 | AccountFormController(this.type, this.action, this.currentRow); 25 | 26 | @override 27 | void onInit() { 28 | super.onInit(); 29 | reset(); 30 | } 31 | 32 | void reset() { 33 | valid = action != 1; 34 | if (action == 2) { 35 | form = { ...currentRow }; 36 | nameFormz = NotEmptyFormz.dirty(value: currentRow['name']); 37 | balanceFormz = NotEmptyNumFormz.dirty(value: removeDecimalZero(currentRow['balance'])); 38 | } 39 | if (action == 1) { 40 | form = {}; 41 | form['currencyCode'] = Get.find().initState['group']['defaultCurrencyCode']; 42 | form['canExpense'] = true; 43 | form['canIncome'] = true; 44 | form['canTransferFrom'] = true; 45 | form['canTransferTo'] = true; 46 | form['include'] = true; 47 | } 48 | update(); 49 | } 50 | 51 | void submit() async { 52 | if (valid) { 53 | try { 54 | Message.showLoading(); 55 | bool result = false; 56 | switch (action) { 57 | case 1: 58 | result = await BaseRepository.add('accounts', { 59 | ...form, 60 | 'type': type 61 | }); 62 | break; 63 | case 2: 64 | result = await BaseRepository.update('accounts', currentRow['id'], form); 65 | break; 66 | } 67 | if (result) { 68 | Get.back(); 69 | Get.find().reload(); 70 | Get.find().load(); 71 | } 72 | } catch (_) { 73 | _.printError(); 74 | } finally { 75 | // Message.disLoading(); 76 | } 77 | } 78 | } 79 | 80 | void changeCurrency(dynamic value) { 81 | form['currencyCode'] = value; 82 | update(); 83 | Get.back(); 84 | } 85 | 86 | void changeName(String value) { 87 | nameFormz = NotEmptyFormz.dirty(value: value); 88 | valid = Formz.validate([nameFormz, balanceFormz]); 89 | form['name'] = value; 90 | update(); 91 | } 92 | 93 | void changeBalance(String value) { 94 | balanceFormz = NotEmptyNumFormz.dirty(value: value); 95 | valid = Formz.validate([nameFormz, balanceFormz]); 96 | form['balance'] = value; 97 | update(); 98 | } 99 | 100 | void changeType(String value) { 101 | type = value; 102 | update(); 103 | if(Get.isBottomSheetOpen ?? false){ 104 | Get.back(); 105 | } 106 | } 107 | 108 | } -------------------------------------------------------------------------------- /lib/app/modules/accounts/controllers/accounts_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:moneynote/app/core/utils/utils.dart'; 2 | import 'package:pull_to_refresh/pull_to_refresh.dart'; 3 | import '/app/core/base/base_repository.dart'; 4 | import '/app/core/values/app_const.dart'; 5 | import '/app/core/base/enums.dart'; 6 | import '/app/core/base/base_controller.dart'; 7 | 8 | class AccountsController extends BaseController { 9 | 10 | LoadDataStatus status = LoadDataStatus.initial; 11 | List> items = []; 12 | Map query = { 13 | AppConst.pageSizeParameter: AppConst.defaultPageSize 14 | }; 15 | late RefreshController refreshController; 16 | int tabIndex = 0; 17 | 18 | void tabClick(int index) { 19 | tabIndex = index; 20 | update(); 21 | reload(); 22 | } 23 | 24 | @override 25 | void onInit() { 26 | super.onInit(); 27 | refreshController = RefreshController(initialRefresh: false); 28 | reload(); 29 | } 30 | 31 | void reload() async { 32 | try { 33 | status = LoadDataStatus.progress; 34 | update(); 35 | query[AppConst.pageParameter] = AppConst.pageStart; 36 | query['type'] = accountTabIndexToType(tabIndex); 37 | items = await BaseRepository.query1('accounts', query); 38 | if (items.length < AppConst.defaultPageSize) { 39 | refreshController.loadNoData(); 40 | } 41 | if (items.isNotEmpty) { 42 | status = LoadDataStatus.success; 43 | } else { 44 | status = LoadDataStatus.empty; 45 | } 46 | refreshController.loadComplete(); 47 | update(); 48 | } catch (_) { 49 | status = LoadDataStatus.failure; 50 | update(); 51 | } 52 | } 53 | 54 | void loadMore() async { 55 | try { 56 | query[AppConst.pageParameter] = query[AppConst.pageParameter] + 1; 57 | query['type'] = accountTabIndexToType(tabIndex); 58 | final newItems = await BaseRepository.query1('accounts', query); 59 | if (newItems.isNotEmpty) { 60 | items = List.of(items)..addAll(newItems); 61 | refreshController.loadComplete(); 62 | } else { 63 | refreshController.loadNoData(); 64 | } 65 | update(); 66 | } catch (_) { 67 | refreshController.loadFailed(); 68 | update(); 69 | } 70 | } 71 | 72 | @override 73 | void dispose() { 74 | super.dispose(); 75 | refreshController.dispose(); 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /lib/app/modules/accounts/data/account_repository.dart: -------------------------------------------------------------------------------- 1 | import '../../../network/http.dart'; 2 | 3 | class AccountRepository { 4 | 5 | static Future createAdjust(int id, Map form) async { 6 | return (await Http.post('accounts/$id/adjust', data: form))['success']; 7 | } 8 | 9 | static Future updateAdjust(int id, Map form) async { 10 | return (await Http.put('accounts/$id/adjust', data: form))['success']; 11 | } 12 | 13 | static Future> balanceView() async { 14 | dynamic data = (await Http.get('accounts/overview'))['data']; 15 | return List.from(data); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /lib/app/modules/charts/chart_repository.dart: -------------------------------------------------------------------------------- 1 | import '../../network/http.dart'; 2 | 3 | class ChartRepository { 4 | 5 | static Future>>> balance() async { 6 | dynamic data = (await Http.get('reports/balance'))['data']; 7 | return [List>.from(data[0]), List>.from(data[1])]; 8 | } 9 | 10 | static Future>> expenseCategory(Map form) async { 11 | dynamic data = (await Http.get('reports/expense-category', params: form))['data']; 12 | return List>.from(data); 13 | } 14 | 15 | static Future>> incomeCategory(Map form) async { 16 | dynamic data = (await Http.get('reports/income-category', params: form))['data']; 17 | return List>.from(data); 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /lib/app/modules/charts/widgets/circular_legend.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '/app/core/utils/utils.dart'; 3 | 4 | class CircularLegend extends StatelessWidget { 5 | 6 | final List> xys; 7 | 8 | const CircularLegend({ 9 | super.key, 10 | required this.xys 11 | }); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | final theme = Theme.of(context); 16 | return ListView.separated( 17 | shrinkWrap: true, 18 | physics: const NeverScrollableScrollPhysics(), 19 | itemCount: xys.length, 20 | separatorBuilder: (_, __) => const Divider(), 21 | itemBuilder: (context, index) { 22 | Map xy = xys[index]; 23 | return ListTile( 24 | dense: true, 25 | visualDensity: const VisualDensity(horizontal: 0, vertical: -4), 26 | title: Text(xy['x'], style: theme.textTheme.titleSmall), 27 | subtitle: Text(removeDecimalZero(xy['y']), style: theme.textTheme.bodySmall), 28 | trailing: Text("${removeDecimalZero(xy['percent'])}%", style: theme.textTheme.titleMedium), 29 | ); 30 | } 31 | ); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /lib/app/modules/charts/widgets/filter/account.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/generated/locales.g.dart'; 4 | import '../../charts_controller.dart'; 5 | import '/app/core/components/form/my_select.dart'; 6 | import '../../../common/select/select_controller.dart'; 7 | import '../../../common/select/select_option.dart'; 8 | 9 | class Account extends StatelessWidget { 10 | 11 | const Account({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return GetBuilder(builder: (controller) { 16 | return MySelect( 17 | label: LocaleKeys.menu_account.tr, 18 | value: controller.query['account'], 19 | allowClear: true, 20 | onClear: () { 21 | controller.query['account'] = null; 22 | controller.update(); 23 | }, 24 | onFocus: () { 25 | Get.find().load('accounts'); 26 | Get.to(() => SelectOption( 27 | title: LocaleKeys.menu_account.tr, 28 | value: controller.query['account'], 29 | onSelect: (value) { 30 | controller.query['account'] = value; 31 | controller.update(); 32 | Get.back(); 33 | }, 34 | )); 35 | }, 36 | ); 37 | }); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /lib/app/modules/charts/widgets/filter/book.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/app/modules/charts/charts_controller.dart'; 4 | import '/app/modules/common/select/select_option.dart'; 5 | import '../../../common/select/select_controller.dart'; 6 | import '/generated/locales.g.dart'; 7 | import '/app/core/components/form/my_select.dart'; 8 | 9 | class Book extends StatelessWidget { 10 | 11 | const Book({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return GetBuilder(builder: (controller) { 16 | return MySelect( 17 | label: LocaleKeys.book_whichBook.tr, 18 | value: controller.query['book'], 19 | onFocus: () { 20 | Get.find().load('books'); 21 | Get.to(() => SelectOption( 22 | title: LocaleKeys.book_whichBook.tr, 23 | value: controller.query['book'], 24 | onSelect: (value) { 25 | controller.query['book'] = value; 26 | controller.update(); 27 | Get.back(); 28 | }, 29 | )); 30 | }, 31 | ); 32 | }); 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /lib/app/modules/charts/widgets/filter/category.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../../charts_controller.dart'; 4 | import '../../../common/select/tree_select_option.dart'; 5 | import '/generated/locales.g.dart'; 6 | import '/app/core/components/form/my_select.dart'; 7 | import '../../../common/select/select_controller.dart'; 8 | 9 | class Category extends StatelessWidget { 10 | 11 | const Category({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return GetBuilder(builder: (controller) { 16 | return MySelect( 17 | multiple: true, 18 | label: LocaleKeys.flow_category.tr, 19 | value: controller.query['categories'], 20 | allowClear: true, 21 | onClear: () { 22 | controller.query['categories'] = null; 23 | controller.update(); 24 | }, 25 | onFocus: () { 26 | Map query = { }; 27 | if (controller.query['book'] != null) { 28 | query['bookId'] = controller.query['book']['value']; 29 | } 30 | if (controller.tabIndex == 0) { 31 | query['type'] = 'EXPENSE'; 32 | } else if (controller.tabIndex == 1) { 33 | query['type'] = 'INCOME'; 34 | } 35 | Get.find().load('categories', params: query); 36 | Get.to(() => TreeSelectOption( 37 | title: LocaleKeys.flow_category.tr, 38 | values: controller.query['categories'], 39 | onSelect: (values) { 40 | controller.query['categories'] = values; 41 | controller.update(); 42 | Get.back(); 43 | }, 44 | ), fullscreenDialog: true); 45 | }, 46 | ); 47 | }); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /lib/app/modules/charts/widgets/filter/filter_tag.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../../charts_controller.dart'; 4 | import '../../../common/select/tree_select_option.dart'; 5 | import '/generated/locales.g.dart'; 6 | import '/app/core/components/form/my_select.dart'; 7 | import '../../../common/select/select_controller.dart'; 8 | 9 | class FilterTag extends StatelessWidget { 10 | 11 | const FilterTag({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return GetBuilder(builder: (controller) { 16 | return MySelect( 17 | multiple: true, 18 | label: LocaleKeys.flow_tag.tr, 19 | value: controller.query['tags'], 20 | allowClear: true, 21 | onClear: () { 22 | controller.query['tags'] = null; 23 | controller.update(); 24 | }, 25 | onFocus: () { 26 | Map query = { }; 27 | if (controller.query['book'] != null) { 28 | query['bookId'] = controller.query['book']['value']; 29 | } 30 | if (controller.tabIndex == 0) { 31 | query['canExpense'] = true; 32 | } else if (controller.tabIndex == 1) { 33 | query['canIncome'] = true; 34 | } 35 | Get.find().load('tags', params: query); 36 | Get.to(() => TreeSelectOption( 37 | title: LocaleKeys.flow_tag.tr, 38 | values: controller.query['tags'], 39 | onSelect: (values) { 40 | controller.query['tags'] = values; 41 | controller.update(); 42 | Get.back(); 43 | }, 44 | )); 45 | }, 46 | ); 47 | }); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /lib/app/modules/charts/widgets/filter/filter_title.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../../charts_controller.dart'; 4 | import '/generated/locales.g.dart'; 5 | import '/app/core/components/form/my_form_text.dart'; 6 | 7 | class FilterTitle extends StatelessWidget { 8 | 9 | const FilterTitle({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return GetBuilder(builder: (controller) { 14 | return MyFormText( 15 | label: LocaleKeys.flow_title.tr, 16 | value: controller.query['title'], 17 | onChange: (value) { 18 | controller.query['title'] = value; 19 | controller.update(); 20 | }, 21 | ); 22 | }); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /lib/app/modules/charts/widgets/filter/index.dart: -------------------------------------------------------------------------------- 1 | export 'book.dart'; 2 | export 'category.dart'; 3 | export 'filter_tag.dart'; 4 | export 'account.dart'; 5 | export 'filter_title.dart'; 6 | export 'min_time.dart'; 7 | export 'max_time.dart'; 8 | export 'payee.dart'; -------------------------------------------------------------------------------- /lib/app/modules/charts/widgets/filter/max_time.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../../charts_controller.dart'; 4 | import '/app/core/components/form/my_form_date.dart'; 5 | import '/generated/locales.g.dart'; 6 | 7 | class MaxTime extends StatelessWidget { 8 | 9 | const MaxTime({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return GetBuilder(builder: (controller) { 14 | return MyFormDate( 15 | label: LocaleKeys.common_maxTime.tr, 16 | value: controller.query['maxTime'], 17 | andTime: false, 18 | onChange: (value) { 19 | controller.query['maxTime'] = value; 20 | controller.update(); 21 | }, 22 | allowClear: true, 23 | onClear: () { 24 | controller.query['maxTime'] = null; 25 | controller.update(); 26 | }, 27 | ); 28 | }); 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /lib/app/modules/charts/widgets/filter/min_time.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../../charts_controller.dart'; 4 | import '/app/core/components/form/my_form_date.dart'; 5 | import '/generated/locales.g.dart'; 6 | 7 | class MinTime extends StatelessWidget { 8 | 9 | const MinTime({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return GetBuilder(builder: (controller) { 14 | return MyFormDate( 15 | label: LocaleKeys.common_minTime.tr, 16 | value: controller.query['minTime'], 17 | andTime: false, 18 | onChange: (value) { 19 | controller.query['minTime'] = value; 20 | controller.update(); 21 | }, 22 | allowClear: true, 23 | onClear: () { 24 | controller.query['minTime'] = null; 25 | controller.update(); 26 | }, 27 | ); 28 | }); 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /lib/app/modules/charts/widgets/filter/payee.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../../charts_controller.dart'; 4 | import '/generated/locales.g.dart'; 5 | import '/app/core/components/form/my_select.dart'; 6 | import '../../../common/select/select_controller.dart'; 7 | import '../../../common/select/select_option.dart'; 8 | 9 | class Payee extends StatelessWidget { 10 | 11 | const Payee({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return GetBuilder(builder: (controller) { 16 | return MySelect( 17 | label: LocaleKeys.flow_payee.tr, 18 | value: controller.query['payees'], 19 | allowClear: true, 20 | onClear: () { 21 | controller.query['payees'] = null; 22 | controller.update(); 23 | }, 24 | onFocus: () { 25 | if (controller.query['type'] == 'TRANSFER' || controller.query['type'] == 'ADJUST') { 26 | //调整余额,则标签为空 27 | Get.find().clear(); 28 | } else { 29 | Map query = { }; 30 | if (controller.query['book'] != null) { 31 | query['bookId'] = controller.query['book']['value']; 32 | } 33 | if (controller.tabIndex == 0) { 34 | query['canExpense'] = true; 35 | } else if (controller.tabIndex == 1) { 36 | query['canIncome'] = true; 37 | } 38 | Get.find().load('payees', params: query); 39 | } 40 | Get.to(() => SelectOption( 41 | title: LocaleKeys.flow_payee.tr, 42 | value: controller.query['payees'], 43 | onSelect: (value) { 44 | controller.query['payees'] = value; 45 | controller.update(); 46 | Get.back(); 47 | }, 48 | )); 49 | }, 50 | ); 51 | }); 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /lib/app/modules/common/select/select_controller.dart: -------------------------------------------------------------------------------- 1 | import '/app/core/base/base_controller.dart'; 2 | import '/app/core/base/base_repository.dart'; 3 | import '/app/core/base/enums.dart'; 4 | 5 | class SelectController extends BaseController { 6 | 7 | LoadDataStatus status = LoadDataStatus.initial; 8 | List options = []; 9 | 10 | void load(String prefix, {Map? params}) async { 11 | try { 12 | status = LoadDataStatus.progress; 13 | options = []; 14 | update(); 15 | options = await BaseRepository.queryAll(prefix, params: params); 16 | if (options.isEmpty) { 17 | status = LoadDataStatus.empty; 18 | } else { 19 | status = LoadDataStatus.success; 20 | } 21 | update(); 22 | } catch (_) { 23 | status = LoadDataStatus.failure; 24 | update(); 25 | } 26 | } 27 | 28 | void loadStatic(values) { 29 | options = values; 30 | if (options.isEmpty) { 31 | status = LoadDataStatus.empty; 32 | } else { 33 | status = LoadDataStatus.success; 34 | } 35 | update(); 36 | } 37 | 38 | void clear() { 39 | status = LoadDataStatus.empty; 40 | options = []; 41 | update(); 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /lib/app/modules/common/select/select_option.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/app/modules/common/select/select_controller.dart'; 4 | import '/app/core/components/form/my_option.dart'; 5 | 6 | class SelectOption extends StatelessWidget { 7 | 8 | final dynamic value; 9 | final Function onSelect; 10 | final String title; 11 | 12 | const SelectOption({ 13 | super.key, 14 | this.value = const { }, 15 | required this.onSelect, 16 | required this.title, 17 | }); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return GetBuilder(builder: (controller) { 22 | return MyOption( 23 | status: controller.status, 24 | options: controller.options, 25 | value: value, 26 | onSelect: onSelect, 27 | pageTitle: title, 28 | ); 29 | }); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /lib/app/modules/common/select/tree_select_option.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/app/core/components/form/my_tree_option.dart'; 4 | import '/app/modules/common/select/select_controller.dart'; 5 | 6 | class TreeSelectOption extends StatelessWidget { 7 | 8 | final String title; 9 | final List? values; 10 | final Function onSelect; 11 | 12 | const TreeSelectOption({ 13 | super.key, 14 | required this.title, 15 | required this.onSelect, 16 | required this.values, 17 | }); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return GetBuilder(builder: (controller) { 22 | return MyTreeOption( 23 | pageTitle: title, 24 | status: controller.status, 25 | options: controller.options, 26 | values: values, 27 | onSelect: onSelect, 28 | ); 29 | }); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/controllers/flow_detail_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import '/generated/locales.g.dart'; 3 | import '../flow_repository.dart'; 4 | import '/app/core/utils/message.dart'; 5 | import '/app/core/base/base_repository.dart'; 6 | import '/app/core/base/enums.dart'; 7 | import '/app/core/base/base_controller.dart'; 8 | import 'flows_controller.dart'; 9 | 10 | class FlowDetailController extends BaseController { 11 | 12 | int id; 13 | LoadDataStatus status = LoadDataStatus.initial; 14 | Map item = {}; 15 | LoadDataStatus deleteStatus = LoadDataStatus.initial; 16 | 17 | FlowDetailController(this.id); 18 | 19 | @override 20 | void onInit() { 21 | super.onInit(); 22 | load(); 23 | loadFiles(); 24 | } 25 | 26 | void setId(newId) { 27 | id = newId; 28 | update(); 29 | } 30 | 31 | void load() async { 32 | try { 33 | status = LoadDataStatus.progress; 34 | update(); 35 | item = await BaseRepository.get('balance-flows', id); 36 | status = LoadDataStatus.success; 37 | update(); 38 | } catch (_) { 39 | status = LoadDataStatus.failure; 40 | update(); 41 | } 42 | } 43 | 44 | void delete() async { 45 | try { 46 | Message.showLoading(); 47 | final result = await BaseRepository.delete('balance-flows', id); 48 | if (result) { 49 | Get.back(); 50 | Get.find().reload(); 51 | } 52 | } catch (_) { 53 | _.printError(); 54 | } 55 | } 56 | 57 | void confirm() async { 58 | try { 59 | Message.showLoading(); 60 | final result = await BaseRepository.action('balance-flows/${item['id']}/confirm'); 61 | if (result) { 62 | Get.find().load(); 63 | } 64 | } catch (_) { 65 | _.printError(); 66 | } 67 | } 68 | 69 | // 文件 70 | LoadDataStatus fileStatus = LoadDataStatus.initial; 71 | List> fileItems = []; 72 | LoadDataStatus deleteFileStatus = LoadDataStatus.initial; 73 | LoadDataStatus uploadFileStatus = LoadDataStatus.initial; 74 | 75 | void loadFiles() async { 76 | try { 77 | fileStatus = LoadDataStatus.progress; 78 | update(); 79 | fileItems = await FlowRepository.getFiles(id); 80 | status = LoadDataStatus.success; 81 | update(); 82 | } catch (_) { 83 | status = LoadDataStatus.failure; 84 | update(); 85 | } 86 | } 87 | 88 | void deleteFile(fileId) async { 89 | try { 90 | Message.showLoading(); 91 | final result = await BaseRepository.delete('flow-files', fileId); 92 | if (result) { 93 | Get.find().loadFiles(); 94 | } 95 | } catch (_) { 96 | _.printError(); 97 | } 98 | } 99 | 100 | void uploadFile(filePath) async { 101 | try { 102 | Message.showLoading(msg: LocaleKeys.common_uploading.tr); 103 | final result = await FlowRepository.uploadFile(id, filePath); 104 | if (result) { 105 | Get.find().loadFiles(); 106 | } 107 | } catch (_) { 108 | Message.error(_.toString()); 109 | _.printError(); 110 | } 111 | Message.disLoading(); 112 | } 113 | 114 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/flow_filter_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/app/modules/flows/widgets/filter/index.dart'; 4 | import '/app/modules/flows/controllers/flows_controller.dart'; 5 | import '/generated/locales.g.dart'; 6 | import '/app/core/components/my_form_page.dart'; 7 | 8 | class FlowFilterPage extends StatelessWidget { 9 | 10 | const FlowFilterPage({super.key}); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return MyFormPage( 15 | title: Text(LocaleKeys.flow_filterPageTitle.tr), 16 | actions: [ 17 | IconButton( 18 | icon: const Icon(Icons.done), 19 | onPressed: () { 20 | Get.find().reload(); 21 | Get.back(); 22 | }, 23 | ) 24 | ], 25 | children: [ 26 | const Book(), 27 | const Type(), 28 | const FilterTitle(), 29 | const Account(), 30 | const Payee(), 31 | const Category(), 32 | const FilterTag(), 33 | const MinTime(), 34 | const MaxTime(), 35 | const MinAmount(), 36 | const MaxAmount(), 37 | const Confirm(), 38 | const Include(), 39 | const HasFile(), 40 | const Notes(), 41 | const SizedBox(height: 70), 42 | SizedBox( 43 | width: double.infinity, 44 | child: ElevatedButton.icon( 45 | icon: const Icon(Icons.done), 46 | onPressed: () { 47 | Get.find().reload(); 48 | Get.back(); 49 | }, 50 | label: Text(LocaleKeys.common_submit.tr) 51 | ), 52 | ), 53 | SizedBox( 54 | width: double.infinity, 55 | child: ElevatedButton.icon( 56 | icon: const Icon(Icons.refresh), 57 | onPressed: () { 58 | Get.find().reset(); 59 | }, 60 | label: Text(LocaleKeys.common_reset.tr) 61 | ), 62 | ) 63 | ] 64 | ); 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/flow_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:http_parser/http_parser.dart'; 3 | import '/app/core/values/app_values.dart'; 4 | import '../../network/http.dart'; 5 | 6 | 7 | class FlowRepository { 8 | 9 | static Future>> getFiles(int id) async { 10 | List data = (await Http.get('balance-flows/$id/files'))['data']; 11 | return List>.from(data); 12 | } 13 | 14 | static Future uploadFile(int id, String filePath) async { 15 | MultipartFile file = await MultipartFile.fromFile(filePath, contentType: MediaType('image', 'jpeg')); 16 | return (await Http.post('balance-flows/$id/addFile', data: FormData.fromMap({ 17 | 'id': id, 18 | 'file': file 19 | })))['success']; 20 | } 21 | 22 | static String buildUrl(Map file) { 23 | return '${AppValues.apiUrl}/api/v1/flow-files/view?id=${file['id']}&createTime=${file['createTime']}'; 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/filter/account.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/generated/locales.g.dart'; 4 | import '/app/core/components/form/my_select.dart'; 5 | import '../../../common/select/select_controller.dart'; 6 | import '../../../common/select/select_option.dart'; 7 | import '../../controllers/flows_controller.dart'; 8 | 9 | class Account extends StatelessWidget { 10 | 11 | const Account({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return GetBuilder(builder: (controller) { 16 | return MySelect( 17 | label: LocaleKeys.menu_account.tr, 18 | value: controller.query['account'], 19 | allowClear: true, 20 | onClear: () { 21 | controller.query['account'] = null; 22 | controller.update(); 23 | }, 24 | onFocus: () { 25 | Get.find().load('accounts'); 26 | Get.to(() => SelectOption( 27 | title: LocaleKeys.menu_account.tr, 28 | value: controller.query['account'], 29 | onSelect: (value) { 30 | controller.query['account'] = value; 31 | controller.update(); 32 | Get.back(); 33 | }, 34 | )); 35 | }, 36 | ); 37 | }); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/filter/book.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/app/modules/common/select/select_option.dart'; 4 | import '../../../common/select/select_controller.dart'; 5 | import '/generated/locales.g.dart'; 6 | import '/app/core/components/form/my_select.dart'; 7 | import '../../controllers/flows_controller.dart'; 8 | 9 | class Book extends StatelessWidget { 10 | 11 | const Book({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return GetBuilder(builder: (controller) { 16 | return MySelect( 17 | label: LocaleKeys.book_whichBook.tr, 18 | value: controller.query['book'], 19 | allowClear: true, 20 | onClear: () { 21 | controller.query['book'] = null; 22 | controller.update(); 23 | }, 24 | onFocus: () { 25 | Get.find().load('books'); 26 | Get.to(() => SelectOption( 27 | title: LocaleKeys.book_whichBook.tr, 28 | value: controller.query['book'], 29 | onSelect: (value) { 30 | controller.query['book'] = value; 31 | controller.update(); 32 | Get.back(); 33 | }, 34 | )); 35 | }, 36 | ); 37 | }); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/filter/category.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../../../common/select/tree_select_option.dart'; 4 | import '/generated/locales.g.dart'; 5 | import '/app/core/components/form/my_select.dart'; 6 | import '../../../common/select/select_controller.dart'; 7 | import '../../controllers/flows_controller.dart'; 8 | 9 | class Category extends StatelessWidget { 10 | 11 | const Category({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return GetBuilder(builder: (controller) { 16 | return MySelect( 17 | multiple: true, 18 | label: LocaleKeys.flow_category.tr, 19 | value: controller.query['categories'], 20 | allowClear: true, 21 | onClear: () { 22 | controller.query['categories'] = null; 23 | controller.update(); 24 | }, 25 | onFocus: () { 26 | Map query = { }; 27 | if (controller.query['book'] != null) { 28 | query['bookId'] = controller.query['book']['value']; 29 | } 30 | if (controller.query['type'] != null) { 31 | query['type'] = controller.query['type']; 32 | } 33 | Get.find().load('categories', params: query); 34 | Get.to(() => TreeSelectOption( 35 | title: LocaleKeys.flow_category.tr, 36 | values: controller.query['categories'], 37 | onSelect: (values) { 38 | controller.query['categories'] = values; 39 | controller.update(); 40 | Get.back(); 41 | }, 42 | )); 43 | }, 44 | ); 45 | }); 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/filter/confirm.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/app/core/components/form/my_select.dart'; 4 | import '/app/core/utils/utils.dart'; 5 | import '/app/core/components/bottomsheet_container.dart'; 6 | import '/generated/locales.g.dart'; 7 | import '../../controllers/flows_controller.dart'; 8 | 9 | class Confirm extends StatelessWidget { 10 | 11 | const Confirm({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return GetBuilder(builder: (controller) { 16 | return MySelect( 17 | label: LocaleKeys.flow_confirm.tr, 18 | value: controller.query['confirm'] != null ? { 19 | 'value': controller.query['confirm'], 20 | 'label': boolToString(controller.query['confirm']) 21 | } : null, 22 | onFocus: () { 23 | Get.bottomSheet( 24 | BottomSheetContainer( 25 | child: Wrap( 26 | children: [ 27 | ListTile( 28 | title: Text(LocaleKeys.common_yes.tr), 29 | onTap: () { 30 | controller.query['confirm'] = true; 31 | controller.update(); 32 | if(Get.isBottomSheetOpen ?? false){ 33 | Get.back(); 34 | } 35 | }, 36 | selected: controller.query['confirm'] == true, 37 | ), 38 | ListTile( 39 | title: Text(LocaleKeys.common_no.tr), 40 | onTap: () { 41 | controller.query['confirm'] = false; 42 | controller.update(); 43 | if(Get.isBottomSheetOpen ?? false){ 44 | Get.back(); 45 | } 46 | }, 47 | selected: controller.query['confirm'] == false, 48 | ), 49 | ] 50 | ) 51 | ) 52 | ); 53 | }, 54 | allowClear: true, 55 | onClear: () { 56 | controller.query['confirm'] = null; 57 | controller.update(); 58 | }, 59 | ); 60 | }); 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/filter/filter_tag.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../../../common/select/tree_select_option.dart'; 4 | import '/generated/locales.g.dart'; 5 | import '/app/core/components/form/my_select.dart'; 6 | import '../../../common/select/select_controller.dart'; 7 | import '../../controllers/flows_controller.dart'; 8 | 9 | class FilterTag extends StatelessWidget { 10 | 11 | const FilterTag({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return GetBuilder(builder: (controller) { 16 | return MySelect( 17 | multiple: true, 18 | label: LocaleKeys.flow_tag.tr, 19 | value: controller.query['tags'], 20 | allowClear: true, 21 | onClear: () { 22 | controller.query['tags'] = null; 23 | controller.update(); 24 | }, 25 | onFocus: () { 26 | if (controller.query['type'] == 'ADJUST') { 27 | //调整余额,则标签为空 28 | Get.find().clear(); 29 | } else { 30 | Map query = { }; 31 | if (controller.query['book'] != null) { 32 | query['bookId'] = controller.query['book']['value']; 33 | } 34 | if (controller.query['type'] == 'EXPENSE') { 35 | query['canExpense'] = true; 36 | } 37 | if (controller.query['type'] == 'INCOME') { 38 | query['canIncome'] = true; 39 | } 40 | if (controller.query['type'] == 'TRANSFER') { 41 | query['canTransfer'] = true; 42 | } 43 | Get.find().load('tags', params: query); 44 | } 45 | Get.to(() => TreeSelectOption( 46 | title: LocaleKeys.flow_tag.tr, 47 | values: controller.query['tags'], 48 | onSelect: (values) { 49 | controller.query['tags'] = values; 50 | controller.update(); 51 | Get.back(); 52 | }, 53 | )); 54 | }, 55 | ); 56 | }); 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/filter/filter_title.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/generated/locales.g.dart'; 4 | import '/app/core/components/form/my_form_text.dart'; 5 | import '../../controllers/flows_controller.dart'; 6 | 7 | class FilterTitle extends StatelessWidget { 8 | 9 | const FilterTitle({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return GetBuilder(builder: (controller) { 14 | return MyFormText( 15 | label: LocaleKeys.flow_title.tr, 16 | value: controller.query['title'], 17 | onChange: (value) { 18 | controller.query['title'] = value; 19 | controller.update(); 20 | }, 21 | ); 22 | }); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/filter/has_file.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/app/core/components/form/my_select.dart'; 4 | import '/app/core/utils/utils.dart'; 5 | import '/app/core/components/bottomsheet_container.dart'; 6 | import '/generated/locales.g.dart'; 7 | import '../../controllers/flows_controller.dart'; 8 | 9 | class HasFile extends StatelessWidget { 10 | 11 | const HasFile({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return GetBuilder(builder: (controller) { 16 | return MySelect( 17 | label: LocaleKeys.flow_hasFile.tr, 18 | value: controller.query['hasFile'] != null ? { 19 | 'value': controller.query['hasFile'], 20 | 'label': hasToString(controller.query['hasFile']) 21 | } : null, 22 | onFocus: () { 23 | Get.bottomSheet( 24 | BottomSheetContainer( 25 | child: Wrap( 26 | children: [ 27 | ListTile( 28 | title: Text(LocaleKeys.common_has.tr), 29 | onTap: () { 30 | controller.query['hasFile'] = true; 31 | controller.update(); 32 | if(Get.isBottomSheetOpen ?? false){ 33 | Get.back(); 34 | } 35 | }, 36 | selected: controller.query['hasFile'] == true, 37 | ), 38 | ListTile( 39 | title: Text(LocaleKeys.common_none.tr), 40 | onTap: () { 41 | controller.query['hasFile'] = false; 42 | controller.update(); 43 | if(Get.isBottomSheetOpen ?? false){ 44 | Get.back(); 45 | } 46 | }, 47 | selected: controller.query['hasFile'] == false, 48 | ), 49 | ] 50 | ) 51 | ) 52 | ); 53 | }, 54 | allowClear: true, 55 | onClear: () { 56 | controller.query['hasFile'] = null; 57 | controller.update(); 58 | }, 59 | ); 60 | }); 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/filter/include.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/app/core/components/form/my_select.dart'; 4 | import '/app/core/utils/utils.dart'; 5 | import '/app/core/components/bottomsheet_container.dart'; 6 | import '/generated/locales.g.dart'; 7 | import '../../controllers/flows_controller.dart'; 8 | 9 | class Include extends StatelessWidget { 10 | 11 | const Include({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return GetBuilder(builder: (controller) { 16 | return MySelect( 17 | label: LocaleKeys.flow_include.tr, 18 | value: controller.query['include'] != null ? { 19 | 'value': controller.query['include'], 20 | 'label': boolToString(controller.query['include']) 21 | } : null, 22 | onFocus: () { 23 | Get.bottomSheet( 24 | BottomSheetContainer( 25 | child: Wrap( 26 | children: [ 27 | ListTile( 28 | title: Text(LocaleKeys.common_yes.tr), 29 | onTap: () { 30 | controller.query['include'] = true; 31 | controller.update(); 32 | if(Get.isBottomSheetOpen ?? false){ 33 | Get.back(); 34 | } 35 | }, 36 | selected: controller.query['include'] == true, 37 | ), 38 | ListTile( 39 | title: Text(LocaleKeys.common_no.tr), 40 | onTap: () { 41 | controller.query['include'] = false; 42 | controller.update(); 43 | if(Get.isBottomSheetOpen ?? false){ 44 | Get.back(); 45 | } 46 | }, 47 | selected: controller.query['include'] == false, 48 | ), 49 | ] 50 | ) 51 | ) 52 | ); 53 | }, 54 | allowClear: true, 55 | onClear: () { 56 | controller.query['include'] = null; 57 | controller.update(); 58 | }, 59 | ); 60 | }); 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/filter/index.dart: -------------------------------------------------------------------------------- 1 | export 'book.dart'; 2 | export 'type.dart'; 3 | export 'filter_title.dart'; 4 | export 'account.dart'; 5 | export 'payee.dart'; 6 | export 'category.dart'; 7 | export 'filter_tag.dart'; 8 | export 'max_time.dart'; 9 | export 'min_time.dart'; 10 | export 'confirm.dart'; 11 | export 'include.dart'; 12 | export 'has_file.dart'; 13 | export 'notes.dart'; 14 | export 'min_amount.dart'; 15 | export 'max_amount.dart'; 16 | -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/filter/max_amount.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/generated/locales.g.dart'; 4 | import '/app/core/components/form/my_form_text.dart'; 5 | import '../../controllers/flows_controller.dart'; 6 | 7 | class MaxAmount extends StatelessWidget { 8 | 9 | const MaxAmount({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return GetBuilder(builder: (controller) { 14 | return MyFormText( 15 | label: LocaleKeys.flow_maxAmount.tr, 16 | value: controller.query['maxAmount'], 17 | onChange: (value) { 18 | controller.query['maxAmount'] = value; 19 | controller.update(); 20 | }, 21 | ); 22 | }); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/filter/max_time.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/app/core/components/form/my_form_date.dart'; 4 | import '/generated/locales.g.dart'; 5 | import '../../controllers/flows_controller.dart'; 6 | 7 | class MaxTime extends StatelessWidget { 8 | 9 | const MaxTime({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return GetBuilder(builder: (controller) { 14 | return MyFormDate( 15 | label: LocaleKeys.common_maxTime.tr, 16 | value: controller.query['maxTime'], 17 | andTime: false, 18 | onChange: (value) { 19 | controller.query['maxTime'] = value; 20 | controller.update(); 21 | }, 22 | allowClear: true, 23 | onClear: () { 24 | controller.query['maxTime'] = null; 25 | controller.update(); 26 | }, 27 | ); 28 | }); 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/filter/min_amount.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/generated/locales.g.dart'; 4 | import '/app/core/components/form/my_form_text.dart'; 5 | import '../../controllers/flows_controller.dart'; 6 | 7 | class MinAmount extends StatelessWidget { 8 | 9 | const MinAmount({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return GetBuilder(builder: (controller) { 14 | return MyFormText( 15 | label: LocaleKeys.flow_minAmount.tr, 16 | value: controller.query['minAmount'], 17 | onChange: (value) { 18 | controller.query['minAmount'] = value; 19 | controller.update(); 20 | }, 21 | ); 22 | }); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/filter/min_time.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/app/core/components/form/my_form_date.dart'; 4 | import '/generated/locales.g.dart'; 5 | import '../../controllers/flows_controller.dart'; 6 | 7 | class MinTime extends StatelessWidget { 8 | 9 | const MinTime({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return GetBuilder(builder: (controller) { 14 | return MyFormDate( 15 | label: LocaleKeys.common_minTime.tr, 16 | value: controller.query['minTime'], 17 | andTime: false, 18 | onChange: (value) { 19 | controller.query['minTime'] = value; 20 | controller.update(); 21 | }, 22 | allowClear: true, 23 | onClear: () { 24 | controller.query['minTime'] = null; 25 | controller.update(); 26 | }, 27 | ); 28 | }); 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/filter/notes.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/generated/locales.g.dart'; 4 | import '/app/core/components/form/my_form_text.dart'; 5 | import '../../controllers/flows_controller.dart'; 6 | 7 | class Notes extends StatelessWidget { 8 | 9 | const Notes({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return GetBuilder(builder: (controller) { 14 | return MyFormText( 15 | label: LocaleKeys.common_notes.tr, 16 | value: controller.query['notes'], 17 | onChange: (value) { 18 | controller.query['notes'] = value; 19 | controller.update(); 20 | }, 21 | ); 22 | }); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/filter/payee.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/generated/locales.g.dart'; 4 | import '/app/core/components/form/my_select.dart'; 5 | import '../../../common/select/select_controller.dart'; 6 | import '../../../common/select/select_option.dart'; 7 | import '../../controllers/flows_controller.dart'; 8 | 9 | class Payee extends StatelessWidget { 10 | 11 | const Payee({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return GetBuilder(builder: (controller) { 16 | return MySelect( 17 | label: LocaleKeys.flow_payee.tr, 18 | value: controller.query['payees'], 19 | allowClear: true, 20 | onClear: () { 21 | controller.query['payees'] = null; 22 | controller.update(); 23 | }, 24 | onFocus: () { 25 | if (controller.query['type'] == 'TRANSFER' || controller.query['type'] == 'ADJUST') { 26 | //调整余额,则标签为空 27 | Get.find().clear(); 28 | } else { 29 | Map query = { }; 30 | if (controller.query['book'] != null) { 31 | query['bookId'] = controller.query['book']['value']; 32 | } 33 | if (controller.query['type'] == 'EXPENSE') { 34 | query['canExpense'] = true; 35 | } 36 | if (controller.query['type'] == 'INCOME') { 37 | query['canIncome'] = true; 38 | } 39 | Get.find().load('payees', params: query); 40 | } 41 | Get.to(() => SelectOption( 42 | title: LocaleKeys.flow_payee.tr, 43 | value: controller.query['payees'], 44 | onSelect: (value) { 45 | controller.query['payees'] = value; 46 | controller.update(); 47 | Get.back(); 48 | }, 49 | )); 50 | }, 51 | ); 52 | }); 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/filter/type.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/app/core/components/bottomsheet_container.dart'; 4 | import '/app/core/utils/utils.dart'; 5 | import '/generated/locales.g.dart'; 6 | import '/app/core/components/form/my_select.dart'; 7 | import '../../controllers/flows_controller.dart'; 8 | 9 | class Type extends StatelessWidget { 10 | 11 | const Type({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return GetBuilder(builder: (controller) { 16 | return MySelect( 17 | label: LocaleKeys.flow_type.tr, 18 | value: controller.query['type'] != null ? { 19 | 'value': controller.query['type'], 20 | 'label': flowTypeToName(controller.query['type']) 21 | } : null, 22 | onFocus: () { 23 | Get.bottomSheet( 24 | BottomSheetContainer( 25 | child: Wrap( 26 | children: [ 27 | ListTile( 28 | title: Text(LocaleKeys.flow_type1.tr), 29 | onTap: () { 30 | controller.query['type'] = 'EXPENSE'; 31 | controller.update(); 32 | if(Get.isBottomSheetOpen ?? false){ 33 | Get.back(); 34 | } 35 | }, 36 | selected: controller.query['type'] == 'EXPENSE', 37 | ), 38 | ListTile( 39 | title: Text(LocaleKeys.flow_type2.tr), 40 | onTap: () { 41 | controller.query['type'] = 'INCOME'; 42 | controller.update(); 43 | if(Get.isBottomSheetOpen ?? false){ 44 | Get.back(); 45 | } 46 | }, 47 | selected: controller.query['type'] == 'INCOME', 48 | ), 49 | ListTile( 50 | title: Text(LocaleKeys.flow_type3.tr), 51 | onTap: () { 52 | controller.query['type'] = 'TRANSFER'; 53 | controller.update(); 54 | if(Get.isBottomSheetOpen ?? false){ 55 | Get.back(); 56 | } 57 | }, 58 | selected: controller.query['type'] == 'TRANSFER', 59 | ), 60 | ListTile( 61 | title: Text(LocaleKeys.flow_type4.tr), 62 | onTap: () { 63 | controller.query['type'] = 'ADJUST'; 64 | controller.update(); 65 | if(Get.isBottomSheetOpen ?? false){ 66 | Get.back(); 67 | } 68 | }, 69 | selected: controller.query['type'] == 'ADJUST', 70 | ), 71 | ] 72 | ) 73 | ) 74 | ); 75 | }, 76 | allowClear: true, 77 | onClear: () { 78 | controller.query['type'] = null; 79 | controller.update(); 80 | }, 81 | ); 82 | }); 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/flow_file_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:get/get.dart'; 4 | import '/app/core/utils/message.dart'; 5 | import '/app/modules/flows/controllers/flow_detail_controller.dart'; 6 | import '/generated/locales.g.dart'; 7 | import '/app/core/components/dialog_confirm.dart'; 8 | import '/app/core/components/web_view_page.dart'; 9 | import '../flow_repository.dart'; 10 | 11 | class FlowFileList extends StatelessWidget { 12 | 13 | final FlowDetailController controller; 14 | 15 | const FlowFileList({ 16 | super.key, 17 | required this.controller 18 | }); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | if (controller.fileItems.isEmpty) { 23 | return const SizedBox.shrink(); 24 | } 25 | return Row( 26 | mainAxisAlignment: MainAxisAlignment.start, 27 | crossAxisAlignment: CrossAxisAlignment.start, 28 | children: [ 29 | SizedBox( 30 | height: 45, 31 | child: Center( 32 | child: Text(LocaleKeys.flow_file.tr, style: Theme.of(context).textTheme.bodyMedium) 33 | ), 34 | ), 35 | Flexible( 36 | child: Column( 37 | mainAxisAlignment: MainAxisAlignment.start, 38 | crossAxisAlignment: CrossAxisAlignment.start, 39 | children: controller.fileItems.map((e) => ( 40 | Row( 41 | crossAxisAlignment: CrossAxisAlignment.center, 42 | children: [ 43 | Expanded( 44 | child: Align( 45 | alignment: Alignment.centerLeft, 46 | child: TextButton( 47 | onPressed: () { 48 | if (e['contentType'] == 'application/pdf') { 49 | Message.error(LocaleKeys.flow_pdfError.tr); 50 | return; 51 | } 52 | Get.to(() => WebViewPage(title: LocaleKeys.flow_filePreviewTitle.tr, url: FlowRepository.buildUrl(e))); 53 | }, 54 | child: Text(e['originalName']), 55 | ), 56 | ), 57 | ), 58 | TextButton( 59 | onPressed: () { 60 | Clipboard.setData( 61 | ClipboardData(text: FlowRepository.buildUrl(e))).then((_) => Message.success(LocaleKeys.common_operationSuccess.tr) 62 | ); 63 | }, 64 | child: Text(LocaleKeys.flow_fileCopy.tr), 65 | ), 66 | DialogConfirm( 67 | content: LocaleKeys.common_confirmDialogTitle.tr, 68 | child: AbsorbPointer( 69 | child: TextButton( 70 | onPressed: () { }, 71 | child: Text(LocaleKeys.common_delete.tr), 72 | ), 73 | ), 74 | onConfirm: () { 75 | Get.find().deleteFile(e['id']); 76 | } 77 | ), 78 | ], 79 | )) 80 | ).toList(), 81 | ) 82 | ), 83 | ], 84 | ); 85 | } 86 | 87 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/form/account.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../../controllers/flow_form_controller.dart'; 4 | import '/app/modules/common/select/select_option.dart'; 5 | import '../../../common/select/select_controller.dart'; 6 | import '/generated/locales.g.dart'; 7 | import '/app/core/components/form/my_select.dart'; 8 | 9 | class Account extends StatelessWidget { 10 | 11 | final FlowFormController controller; 12 | 13 | const Account({ 14 | super.key, 15 | required this.controller 16 | }); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return MySelect( 21 | required: controller.type == 'TRANSFER', 22 | label: controller.type == 'TRANSFER'? LocaleKeys.flow_from.tr : LocaleKeys.flow_account.tr, 23 | value: controller.form['account'], 24 | onFocus: () { 25 | Map query = { }; 26 | if (controller.type == 'EXPENSE') { 27 | query['canExpense'] = true; 28 | } 29 | if (controller.type == 'INCOME') { 30 | query['canIncome'] = true; 31 | } 32 | if (controller.type == 'TRANSFER') { 33 | query['canTransferFrom'] = true; 34 | } 35 | Get.find().load('accounts', params: query); 36 | Get.to(() => SelectOption( 37 | title: LocaleKeys.menu_account.tr, 38 | value: controller.form['account'], 39 | onSelect: (value) { 40 | controller.form['account'] = value; 41 | controller.checkValid(); 42 | Get.back(); 43 | }, 44 | )); 45 | }, 46 | ); 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/form/amount.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import 'package:moneynote/app/core/utils/utils.dart'; 4 | import '../../controllers/flow_form_controller.dart'; 5 | import '/generated/locales.g.dart'; 6 | import '/app/core/components/form/my_form_text.dart'; 7 | 8 | class Amount extends StatelessWidget { 9 | 10 | final FlowFormController controller; 11 | 12 | const Amount({ 13 | super.key, 14 | required this.controller 15 | }); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Column( 20 | children: controller.form['categories'].map((e) { 21 | return Column( 22 | children: [ 23 | MyFormText( 24 | required: true, 25 | label: "${e['categoryName']} - ${LocaleKeys.flow_amount.tr}", 26 | value: e['amount'], 27 | onChange: (value) { 28 | e['amount'] = value; 29 | controller.checkValid(); 30 | }, 31 | ), 32 | if (controller.needConvert) ...[ 33 | Row( 34 | mainAxisAlignment: MainAxisAlignment.center, 35 | crossAxisAlignment: CrossAxisAlignment.center, 36 | children: [ 37 | Expanded( 38 | child: MyFormText( 39 | required: true, 40 | label: "${e['categoryName']} - ${controller.convertCode}", 41 | value: e['convertedAmount'], 42 | onChange: (value) { 43 | e['convertedAmount'] = value; 44 | controller.checkValid(); 45 | }, 46 | ), 47 | ), 48 | IconButton( 49 | onPressed: () async { 50 | if (validAmount(e['amount'])) { 51 | double? c = await Get.find().calcCurrency(double.parse(e['amount'].toString())); 52 | if (c != null) { 53 | e['convertedAmount'] = c; 54 | controller.checkValid(); 55 | } 56 | } 57 | }, 58 | icon: const Icon(Icons.calculate) 59 | ) 60 | ], 61 | ) 62 | ], 63 | ], 64 | ); 65 | }).toList(), 66 | ); 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/form/book.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../../controllers/flow_form_controller.dart'; 4 | import '/app/modules/common/select/select_option.dart'; 5 | import '../../../common/select/select_controller.dart'; 6 | import '/generated/locales.g.dart'; 7 | import '/app/core/components/form/my_select.dart'; 8 | 9 | class Book extends StatelessWidget { 10 | 11 | final FlowFormController controller; 12 | 13 | const Book({ 14 | super.key, 15 | required this.controller 16 | }); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return MySelect( 21 | required: true, 22 | readOnly: controller.action != 1, 23 | label: LocaleKeys.book_whichBook.tr, 24 | value: controller.form['book'], 25 | onFocus: () { 26 | Get.find().load('books'); 27 | Get.to(() => SelectOption( 28 | title: LocaleKeys.book_whichBook.tr, 29 | value: controller.form['book'], 30 | onSelect: (value) { 31 | Get.find().changeBook(value); 32 | Get.back(); 33 | }, 34 | )); 35 | }, 36 | ); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/form/category.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../../../common/select/tree_select_option.dart'; 4 | import '../../controllers/flow_form_controller.dart'; 5 | import '../../../common/select/select_controller.dart'; 6 | import '/generated/locales.g.dart'; 7 | import '/app/core/components/form/my_select.dart'; 8 | 9 | class Category extends StatelessWidget { 10 | 11 | final FlowFormController controller; 12 | 13 | const Category({ 14 | super.key, 15 | required this.controller 16 | }); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return MySelect( 21 | required: true, 22 | multiple: true, 23 | label: LocaleKeys.flow_category.tr, 24 | value: controller.categories, 25 | onFocus: () { 26 | Map query = { }; 27 | query['bookId'] = controller.form['book']['value']; 28 | query['type'] = controller.type; 29 | Get.find().load('categories', params: query); 30 | Get.to(() => TreeSelectOption( 31 | title: LocaleKeys.flow_category.tr, 32 | values: controller.categories, 33 | onSelect: (values) { 34 | Get.find().changeCategory(values); 35 | Get.back(); 36 | }, 37 | )); 38 | }, 39 | ); 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/form/confirm.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/app/core/components/form/my_form_switch.dart'; 4 | import '../../controllers/flow_form_controller.dart'; 5 | import '/generated/locales.g.dart'; 6 | 7 | class Confirm extends StatelessWidget { 8 | 9 | final FlowFormController controller; 10 | 11 | const Confirm({ 12 | super.key, 13 | required this.controller 14 | }); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return MyFormSwitch( 19 | readOnly: controller.action == 2, 20 | required: true, 21 | label: LocaleKeys.flow_confirm.tr, 22 | value: controller.form['confirm'], 23 | onChange: (value) { 24 | controller.form['confirm'] = value; 25 | controller.update(); 26 | }, 27 | ); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/form/create_time.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../../../../core/components/form/my_form_date.dart'; 4 | import '../../controllers/flow_form_controller.dart'; 5 | import '/generated/locales.g.dart'; 6 | 7 | class CreateTime extends StatelessWidget { 8 | 9 | final FlowFormController controller; 10 | 11 | const CreateTime({ 12 | super.key, 13 | required this.controller 14 | }); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return MyFormDate( 19 | required: true, 20 | label: LocaleKeys.flow_createTime.tr, 21 | value: controller.form['createTime'], 22 | onChange: (value) { 23 | controller.form['createTime'] = value; 24 | controller.update(); 25 | }, 26 | ); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/form/form_tag.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../../../common/select/tree_select_option.dart'; 4 | import '../../controllers/flow_form_controller.dart'; 5 | import '../../../common/select/select_controller.dart'; 6 | import '/generated/locales.g.dart'; 7 | import '/app/core/components/form/my_select.dart'; 8 | 9 | class FormTag extends StatelessWidget { 10 | 11 | final FlowFormController controller; 12 | 13 | const FormTag({ 14 | super.key, 15 | required this.controller 16 | }); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return MySelect( 21 | multiple: true, 22 | label: LocaleKeys.flow_tag.tr, 23 | value: controller.form['tags'], 24 | onFocus: () { 25 | Map query = { }; 26 | query['bookId'] = controller.form['book']['value']; 27 | if (controller.type == 'EXPENSE') { 28 | query['canExpense'] = true; 29 | } 30 | if (controller.type == 'INCOME') { 31 | query['canIncome'] = true; 32 | } 33 | if (controller.type == 'TRANSFER') { 34 | query['canTransfer'] = true; 35 | } 36 | Get.find().load('tags', params: query); 37 | Get.to(() => TreeSelectOption( 38 | title: LocaleKeys.flow_tag.tr, 39 | values: controller.form['tags'], 40 | onSelect: (values) { 41 | controller.form['tags'] = values; 42 | controller.update(); 43 | Get.back(); 44 | }, 45 | )); 46 | }, 47 | ); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/form/form_title.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../../controllers/flow_form_controller.dart'; 4 | import '/generated/locales.g.dart'; 5 | import '/app/core/components/form/my_form_text.dart'; 6 | 7 | class FormTitle extends StatelessWidget { 8 | 9 | final FlowFormController controller; 10 | 11 | const FormTitle({ 12 | super.key, 13 | required this.controller 14 | }); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return MyFormText( 19 | label: LocaleKeys.flow_title.tr, 20 | value: controller.form['title'], 21 | onChange: (value) { 22 | controller.form['title'] = value; 23 | controller.update(); 24 | }, 25 | ); 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/form/include.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/app/core/components/form/my_form_switch.dart'; 4 | import '../../controllers/flow_form_controller.dart'; 5 | import '/generated/locales.g.dart'; 6 | 7 | class Include extends StatelessWidget { 8 | 9 | final FlowFormController controller; 10 | 11 | const Include({ 12 | super.key, 13 | required this.controller 14 | }); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return MyFormSwitch( 19 | required: true, 20 | label: LocaleKeys.flow_include.tr, 21 | value: controller.form['include'], 22 | onChange: (value) { 23 | controller.form['include'] = value; 24 | controller.update(); 25 | }, 26 | ); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/form/index.dart: -------------------------------------------------------------------------------- 1 | export 'book.dart'; 2 | export 'form_title.dart'; 3 | export 'create_time.dart'; 4 | export 'account.dart'; 5 | export 'category.dart'; 6 | export 'amount.dart'; 7 | export 'payee.dart'; 8 | export 'to_account.dart'; 9 | export 'form_tag.dart'; 10 | export 'confirm.dart'; 11 | export 'include.dart'; 12 | export 'notes.dart'; -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/form/notes.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../../controllers/flow_form_controller.dart'; 4 | import '/generated/locales.g.dart'; 5 | import '/app/core/components/form/my_form_text.dart'; 6 | 7 | class Notes extends StatelessWidget { 8 | 9 | final FlowFormController controller; 10 | 11 | const Notes({ 12 | super.key, 13 | required this.controller 14 | }); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return MyFormText( 19 | label: LocaleKeys.common_notes.tr, 20 | value: controller.form['notes'], 21 | onChange: (value) { 22 | controller.form['notes'] = value; 23 | controller.update(); 24 | }, 25 | ); 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/form/payee.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../../controllers/flow_form_controller.dart'; 4 | import '/app/modules/common/select/select_option.dart'; 5 | import '../../../common/select/select_controller.dart'; 6 | import '/generated/locales.g.dart'; 7 | import '/app/core/components/form/my_select.dart'; 8 | 9 | class Payee extends StatelessWidget { 10 | 11 | final FlowFormController controller; 12 | 13 | const Payee({ 14 | super.key, 15 | required this.controller 16 | }); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return MySelect( 21 | label: LocaleKeys.flow_payee.tr, 22 | value: controller.form['payee'], 23 | onFocus: () { 24 | Map query = { }; 25 | query['bookId'] = controller.form['book']['value']; 26 | if (controller.form['type'] == 'EXPENSE') { 27 | query['canExpense'] = true; 28 | } 29 | if (controller.form['type'] == 'INCOME') { 30 | query['canIncome'] = true; 31 | } 32 | Get.find().load('payees', params: query); 33 | Get.to(() => SelectOption( 34 | title: LocaleKeys.menu_account.tr, 35 | value: controller.form['payee'], 36 | onSelect: (value) { 37 | controller.form['payee'] = value; 38 | controller.update(); 39 | Get.back(); 40 | }, 41 | )); 42 | }, 43 | ); 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/form/to_account.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../../controllers/flow_form_controller.dart'; 4 | import '/app/modules/common/select/select_option.dart'; 5 | import '../../../common/select/select_controller.dart'; 6 | import '/generated/locales.g.dart'; 7 | import '/app/core/components/form/my_select.dart'; 8 | 9 | class ToAccount extends StatelessWidget { 10 | 11 | final FlowFormController controller; 12 | 13 | const ToAccount({ 14 | super.key, 15 | required this.controller 16 | }); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return MySelect( 21 | required: true, 22 | label: LocaleKeys.flow_to.tr, 23 | value: controller.form['to'], 24 | onFocus: () { 25 | Get.find().load('accounts', params: { 'canTransferTo': true }); 26 | Get.to(() => SelectOption( 27 | title: LocaleKeys.menu_account.tr, 28 | value: controller.form['to'], 29 | onSelect: (value) { 30 | controller.form['to'] = value; 31 | controller.checkValid(); 32 | Get.back(); 33 | }, 34 | )); 35 | }, 36 | ); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /lib/app/modules/flows/widgets/form/transfer_amount.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../../../../core/utils/utils.dart'; 4 | import '../../controllers/flow_form_controller.dart'; 5 | import '/generated/locales.g.dart'; 6 | import '/app/core/components/form/my_form_text.dart'; 7 | 8 | class TransferAmount extends StatelessWidget { 9 | 10 | final FlowFormController controller; 11 | 12 | const TransferAmount({ 13 | super.key, 14 | required this.controller 15 | }); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Column( 20 | children: [ 21 | MyFormText( 22 | required: true, 23 | label: LocaleKeys.flow_amount.tr, 24 | value: controller.form['amount'], 25 | onChange: (value) { 26 | controller.form['amount'] = value; 27 | controller.checkValid(); 28 | }, 29 | ), 30 | if (controller.needConvert) ...[ 31 | Row( 32 | mainAxisAlignment: MainAxisAlignment.center, 33 | crossAxisAlignment: CrossAxisAlignment.center, 34 | children: [ 35 | Expanded( 36 | child: MyFormText( 37 | required: true, 38 | label: LocaleKeys.account_detailLabelConvert.trParams({'code': controller.convertCode}), 39 | value: controller.form['convertedAmount'], 40 | onChange: (value) { 41 | controller.form['convertedAmount'] = value; 42 | controller.checkValid(); 43 | }, 44 | ), 45 | ), 46 | IconButton( 47 | onPressed: () async { 48 | if (validAmount(controller.form['amount'])) { 49 | double? c = await Get.find().calcCurrency(double.parse(controller.form['amount'].toString())); 50 | if (c != null) { 51 | controller.form['convertedAmount'] = c; 52 | controller.checkValid(); 53 | } 54 | } 55 | }, 56 | icon: const Icon(Icons.calculate) 57 | ) 58 | ], 59 | ) 60 | ] 61 | ], 62 | ); 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /lib/app/modules/login/controllers/auth_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import 'package:moneynote/app/core/utils/utils.dart'; 3 | import '/app/network/http.dart'; 4 | import '/app/core/utils/api_url.dart'; 5 | import '/app/modules/login/data/login_repository.dart'; 6 | import '/app/core/utils/token.dart'; 7 | import '/app/core/base/base_controller.dart'; 8 | import 'login_controller.dart'; 9 | 10 | enum AuthStatus { uninitialized, authenticated, unauthenticated, loading } 11 | 12 | class AuthController extends BaseController { 13 | 14 | AuthStatus status = AuthStatus.uninitialized; 15 | Map initState = const { }; 16 | 17 | void onLoggedIn(String token, String api) async { 18 | await Token.save(token); 19 | await ApiUrl.save(api); 20 | onAppStarted(); 21 | } 22 | 23 | void onLoggedOut() async { 24 | await Token.delete(); 25 | status = AuthStatus.unauthenticated; 26 | initState = const { }; 27 | Get.delete(); 28 | update(); 29 | } 30 | 31 | void onAppStarted() async { 32 | final String token = await Token.get(); 33 | final String apiUrl = await ApiUrl.get(); 34 | if (apiUrl.isNotEmpty && token.isNotEmpty) { 35 | try { 36 | initState = await LoginRepository.getInitState(); 37 | status = AuthStatus.authenticated; 38 | } catch (_) { 39 | status = AuthStatus.unauthenticated; 40 | } 41 | } else { 42 | status = AuthStatus.unauthenticated; 43 | } 44 | update(); 45 | } 46 | 47 | void changeCurrentBook(String id) async { 48 | await Http.patch('setDefaultGroupAndBook/$id'); 49 | initState = await LoginRepository.getInitState(); 50 | reloadState(); 51 | update(); 52 | } 53 | 54 | @override 55 | void onInit() { 56 | super.onInit(); 57 | onAppStarted(); 58 | } 59 | 60 | String groupCurrency() { 61 | return initState['group']['defaultCurrencyCode']; 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /lib/app/modules/login/controllers/login_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:formz/formz.dart'; 3 | import 'package:get/get.dart'; 4 | import 'package:moneynote/generated/locales.g.dart'; 5 | import '../../../core/utils/message.dart'; 6 | import '/app/core/utils/utils.dart'; 7 | import '/app/modules/login/controllers/auth_controller.dart'; 8 | import '../data/login_repository.dart'; 9 | import '/app/core/commons/form/not_empty_formz.dart'; 10 | import '/app/core/base/base_controller.dart'; 11 | import '/app/core/values/app_values.dart'; 12 | 13 | class LoginController extends BaseController { 14 | 15 | bool valid = false; 16 | NotEmptyFormz usernameFormz = const NotEmptyFormz.pure(); 17 | NotEmptyFormz passwordFormz = const NotEmptyFormz.pure(); 18 | NotEmptyFormz apiFormz = const NotEmptyFormz.pure(); 19 | TextEditingController apiController = TextEditingController(); 20 | 21 | AuthController authController = Get.find(); 22 | 23 | @override 24 | void onInit() { 25 | super.onInit(); 26 | if (AppValues.apiUrl.isNotEmpty) { 27 | apiController.value = apiController.value.copyWith(text: AppValues.apiUrl); 28 | apiFormz = NotEmptyFormz.dirty(value: AppValues.apiUrl); 29 | } 30 | } 31 | 32 | void usernameChanged(String username) { 33 | usernameFormz = NotEmptyFormz.dirty(value: username); 34 | valid = Formz.validate([usernameFormz, passwordFormz, apiFormz]); 35 | update(); 36 | } 37 | 38 | void passwordChanged(String password) { 39 | passwordFormz = NotEmptyFormz.dirty(value: password); 40 | valid = Formz.validate([usernameFormz, passwordFormz, apiFormz]); 41 | update(); 42 | } 43 | 44 | void apiChanged(String api) { 45 | apiFormz = NotEmptyFormz.dirty(value: api); 46 | valid = Formz.validate([usernameFormz, passwordFormz, apiFormz]); 47 | AppValues.apiUrl = apiFormz.value; 48 | update(); 49 | } 50 | 51 | void login() async { 52 | if (valid) { 53 | try { 54 | Message.showLoading(msg: LocaleKeys.user_logging.tr); 55 | String token = await LoginRepository.logIn(username: usernameFormz.value, password: passwordFormz.value); 56 | authController.onLoggedIn(token, apiFormz.value); 57 | update(); 58 | reloadState(); 59 | } catch (_) { 60 | _.printError(); 61 | } 62 | } 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /lib/app/modules/login/data/login_repository.dart: -------------------------------------------------------------------------------- 1 | import '/app/network/http.dart'; 2 | 3 | class LoginRepository { 4 | 5 | static Future logIn({ 6 | required String username, 7 | required String password 8 | }) async { 9 | Map response = await Http.post('login', data: {'username': username, 'password': password}); 10 | if (response['success']) { 11 | return response['data']['accessToken']; 12 | } else { 13 | throw Exception('Login Failed'); 14 | // return Future.error('Login Failed'); 15 | } 16 | } 17 | 18 | static Future> getInitState() async { 19 | return (await Http.get('initState'))['data']; 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /lib/app/modules/login/ui/login_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '../../my/controllers/language_controller.dart'; 4 | import '/app/core/components/bottomsheet_container.dart'; 5 | import './widgets/login_form.dart'; 6 | import '/app/core/values/app_values.dart'; 7 | import '/app/core/values/app_text_styles.dart'; 8 | 9 | class LoginPage extends StatelessWidget { 10 | 11 | const LoginPage({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Material( 16 | child: Column( 17 | children: [ 18 | Expanded( 19 | child: Padding( 20 | padding: const EdgeInsets.symmetric(horizontal: 15), 21 | child: Column( 22 | children: [ 23 | Container( 24 | width: double.infinity, 25 | padding: const EdgeInsets.only(top: 50, bottom: 60), 26 | child: Row( 27 | mainAxisAlignment: MainAxisAlignment.center, 28 | children: [ 29 | Image.asset('assets/logo.png', width: 50, height: 50), 30 | const SizedBox(width: 20), 31 | const Text(AppValues.appName, style: AppTextStyle.loginTitle), 32 | ], 33 | ) 34 | ), 35 | const LoginForm(), 36 | ], 37 | ), 38 | ) 39 | ), 40 | Column( 41 | children: [ 42 | IconButton( 43 | icon: const Icon(Icons.language, size: 32), 44 | onPressed: () { 45 | Get.bottomSheet( 46 | BottomSheetContainer(child: GetBuilder(builder: (controller) { 47 | return Wrap( 48 | children: controller.languages.map((e) => 49 | ListTile( 50 | title: Text(e['label']), 51 | onTap: () { 52 | Get.find().changeLang(e['name'], e['locale']); 53 | }, 54 | selected: e['selected'], 55 | ) 56 | ).toList(), 57 | ); 58 | })) 59 | ); 60 | }, 61 | ), 62 | Text(AppValues.version, style: Theme.of(context).textTheme.bodySmall), 63 | const SizedBox(height: 20), 64 | ], 65 | ) 66 | ], 67 | ), 68 | ); 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /lib/app/modules/login/ui/widgets/api_url_input.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/generated/locales.g.dart'; 4 | import '../../controllers/login_controller.dart'; 5 | 6 | class ApiUrlInput extends StatelessWidget { 7 | 8 | const ApiUrlInput({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return GetBuilder(builder: (controller) { 13 | return TextField( 14 | controller: controller.apiController, 15 | onChanged: (value) { Get.find().apiChanged(value); }, 16 | decoration: InputDecoration( 17 | hintText: LocaleKeys.user_apiPlaceholder.tr, 18 | contentPadding: const EdgeInsets.symmetric(horizontal: 10), 19 | errorText: (controller.apiFormz.isPure || controller.apiFormz.isValid) ? null : LocaleKeys.user_apiErrorText.tr, 20 | ), 21 | ); 22 | }); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /lib/app/modules/login/ui/widgets/login_form.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'user_name_input.dart'; 3 | import 'password_name_input.dart'; 4 | import 'api_url_input.dart'; 5 | import 'submit_btn.dart'; 6 | 7 | class LoginForm extends StatelessWidget { 8 | 9 | const LoginForm({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return const Column( 14 | children: [ 15 | UsernameInput(), 16 | SizedBox(height: 10), 17 | PasswordInput(), 18 | SizedBox(height: 10), 19 | ApiUrlInput(), 20 | SizedBox(height: 30), 21 | SizedBox( 22 | width: double.infinity, 23 | height: 50, 24 | child: SubmitBtn(), 25 | ), 26 | ], 27 | ); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /lib/app/modules/login/ui/widgets/password_name_input.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/generated/locales.g.dart'; 4 | import '../../controllers/login_controller.dart'; 5 | 6 | class PasswordInput extends StatelessWidget { 7 | 8 | const PasswordInput({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return GetBuilder(builder: (controller) { 13 | return TextField( 14 | obscureText: true, 15 | obscuringCharacter: "*", 16 | enableSuggestions: false, 17 | autocorrect: false, 18 | onChanged: (value) { Get.find().passwordChanged(value); }, 19 | decoration: InputDecoration( 20 | hintText: LocaleKeys.user_passwordPlaceholder.tr, 21 | contentPadding: const EdgeInsets.symmetric(horizontal: 10), 22 | errorText: (controller.passwordFormz.isPure || controller.passwordFormz.isValid) ? null : LocaleKeys.user_passwordErrorText.tr, 23 | ), 24 | ); 25 | }); 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /lib/app/modules/login/ui/widgets/submit_btn.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/generated/locales.g.dart'; 4 | import '../../controllers/login_controller.dart'; 5 | 6 | class SubmitBtn extends StatelessWidget { 7 | 8 | const SubmitBtn({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return GetBuilder(builder: (controller) { 13 | return ElevatedButton.icon( 14 | icon: const Icon(Icons.login), 15 | onPressed: controller.valid ? () { controller.login(); } : null, 16 | label: Text(LocaleKeys.user_login.tr) 17 | ); 18 | }); 19 | } 20 | } -------------------------------------------------------------------------------- /lib/app/modules/login/ui/widgets/user_name_input.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/generated/locales.g.dart'; 4 | import '../../controllers/login_controller.dart'; 5 | 6 | class UsernameInput extends StatelessWidget { 7 | 8 | const UsernameInput({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return GetBuilder(builder: (controller) { 13 | return TextField( 14 | onChanged: (value) { Get.find().usernameChanged(value); }, 15 | decoration: InputDecoration( 16 | hintText: LocaleKeys.user_usernamePlaceholder.tr, 17 | contentPadding: const EdgeInsets.symmetric(horizontal: 10), 18 | errorText: (controller.usernameFormz.isPure || controller.usernameFormz.isValid) ? null : LocaleKeys.user_usernameErrorText.tr, 19 | ), 20 | ); 21 | }); 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /lib/app/modules/my/controllers/account_overview_controller.dart: -------------------------------------------------------------------------------- 1 | import '/app/modules/accounts/data/account_repository.dart'; 2 | import '/app/core/base/enums.dart'; 3 | import '/app/core/base/base_controller.dart'; 4 | 5 | class AccountOverviewController extends BaseController { 6 | 7 | LoadDataStatus status = LoadDataStatus.initial; 8 | List data = []; 9 | 10 | @override 11 | void onInit() { 12 | super.onInit(); 13 | load(); 14 | } 15 | 16 | void load() async { 17 | try { 18 | status = LoadDataStatus.progress; 19 | update(); 20 | data = await AccountRepository.balanceView(); 21 | status = LoadDataStatus.success; 22 | update(); 23 | } catch (_) { 24 | status = LoadDataStatus.failure; 25 | update(); 26 | } 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /lib/app/modules/my/controllers/api_version_controller.dart: -------------------------------------------------------------------------------- 1 | import '/app/network/http.dart'; 2 | import '/app/core/base/enums.dart'; 3 | import '/app/core/base/base_controller.dart'; 4 | 5 | class ApiVersionController extends BaseController { 6 | 7 | LoadDataStatus status = LoadDataStatus.initial; 8 | String version = 'loading...'; 9 | 10 | @override 11 | void onInit() { 12 | super.onInit(); 13 | load(); 14 | } 15 | 16 | void load() async { 17 | try { 18 | status = LoadDataStatus.progress; 19 | update(); 20 | version = (await Http.get('version'))['data']; 21 | status = LoadDataStatus.success; 22 | update(); 23 | } catch (_) { 24 | status = LoadDataStatus.failure; 25 | update(); 26 | } 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /lib/app/modules/my/controllers/language_controller.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | import 'package:get/get.dart'; 3 | import '../../../network/http.dart'; 4 | import '/app/modules/my/controllers/theme_controller.dart'; 5 | import '/app/core/utils/language.dart'; 6 | import '/app/core/base/base_controller.dart'; 7 | 8 | class LanguageController extends BaseController { 9 | 10 | late List> languages; 11 | String current = 'en_US'; 12 | 13 | @override 14 | void onInit() { 15 | super.onInit(); 16 | initLang(); 17 | setCurrent(); 18 | } 19 | 20 | void setCurrent() async { 21 | String currentLang = await Language.get(); 22 | if (currentLang.isEmpty) { 23 | currentLang = Get.deviceLocale.toString(); 24 | // 只能支持中文或英文 25 | if (currentLang != 'en_US' && currentLang != 'zh_CN') { 26 | currentLang = 'en_US'; 27 | } 28 | } 29 | var currentLanguage = languages.firstWhere((e) => e['name'] == currentLang); 30 | Get.updateLocale(currentLanguage['locale']); 31 | current = currentLanguage['name']; 32 | initLang(); 33 | Get.find().initTheme(); 34 | } 35 | 36 | void initLang() { 37 | languages = [ 38 | { 39 | 'name': 'en_US', 40 | 'label': '🇺🇸 English', 41 | 'locale': const Locale('en', 'US'), 42 | 'selected': current == 'en_US', 43 | }, 44 | { 45 | 'name': 'zh_CN', 46 | 'label': '🇨🇳 简体中文', 47 | 'locale': const Locale('zh', 'CN'), 48 | 'selected': current == 'zh_CN', 49 | }, 50 | ]; 51 | } 52 | 53 | void changeLang(name, locale) { 54 | current = name; 55 | Get.updateLocale(locale); 56 | if(Get.isBottomSheetOpen ?? false){ 57 | Get.back(); 58 | } 59 | Get.find().initTheme(); 60 | Http.init(); 61 | Language.save(name); 62 | initLang(); 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /lib/app/network/http.dart: -------------------------------------------------------------------------------- 1 | import '/app/core/values/app_values.dart'; 2 | import '/app/network/http_client.dart'; 3 | 4 | class Http { 5 | 6 | static Future> get(String uri, {Map? params}) async { 7 | return (await HttpClient().get('${AppValues.apiUrl}/api/v1/$uri', params: params)); 8 | } 9 | 10 | static Future> post(String uri, {data}) async { 11 | return (await HttpClient().post('${AppValues.apiUrl}/api/v1/$uri', data: data)); 12 | } 13 | 14 | static Future> patch(String uri, {data}) async { 15 | return (await HttpClient().patch('${AppValues.apiUrl}/api/v1/$uri', data: data)); 16 | } 17 | 18 | static Future> delete(String uri) async { 19 | return (await HttpClient().delete('${AppValues.apiUrl}/api/v1/$uri')); 20 | } 21 | 22 | static Future> put(String uri, {data}) async { 23 | return (await HttpClient().put('${AppValues.apiUrl}/api/v1/$uri', data: data)); 24 | } 25 | 26 | static void init() { 27 | HttpClient().init(); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /lib/app/routes/app_pages.dart: -------------------------------------------------------------------------------- 1 | import 'package:get/get.dart'; 2 | import '/start_page.dart'; 3 | part 'app_routes.dart'; 4 | 5 | class AppPages { 6 | 7 | AppPages._(); 8 | 9 | static const INITIAL = Routes.START; 10 | 11 | static final routes = [ 12 | GetPage( 13 | name: _Paths.START, 14 | page: () => const StartPage(), 15 | ), 16 | ]; 17 | 18 | } -------------------------------------------------------------------------------- /lib/app/routes/app_routes.dart: -------------------------------------------------------------------------------- 1 | part of 'app_pages.dart'; 2 | // DO NOT EDIT. This is code generated via package:get_cli/get_cli.dart 3 | 4 | abstract class Routes { 5 | Routes._(); 6 | 7 | static const START = _Paths.START; 8 | } 9 | 10 | abstract class _Paths { 11 | static const START = '/start'; 12 | } 13 | -------------------------------------------------------------------------------- /lib/flavors/environment.dart: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/lib/flavors/environment.dart -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'app.dart'; 3 | 4 | void main() { 5 | runApp(const App()); 6 | } 7 | -------------------------------------------------------------------------------- /lib/start_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:get/get.dart'; 3 | import '/app/modules/login/controllers/auth_controller.dart'; 4 | import '/app/core/components/pages/index.dart'; 5 | import '/app/modules/login/controllers/login_controller.dart'; 6 | import '/app/modules/login/ui/login_page.dart'; 7 | import 'index_page.dart'; 8 | 9 | class StartPage extends StatelessWidget { 10 | 11 | const StartPage({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return GetBuilder(builder: (controller) { 16 | switch (controller.status) { 17 | case AuthStatus.uninitialized: 18 | return const LoadingPage(); 19 | case AuthStatus.loading: 20 | return const LoadingPage(); 21 | case AuthStatus.unauthenticated: 22 | Get.put(LoginController()); 23 | return const LoginPage(); 24 | case AuthStatus.authenticated: 25 | return const IndexPage(); 26 | } 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | ThemeData lightTheme = ThemeData.light().copyWith( 4 | useMaterial3: false, 5 | ); 6 | 7 | ThemeData redTheme = ThemeData.light().copyWith( 8 | // primaryColor: Colors.deepPurple, 9 | useMaterial3: false, 10 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.red), 11 | ); 12 | 13 | ThemeData orangeTheme = ThemeData.light().copyWith( 14 | // primaryColor: Colors.deepPurple, 15 | useMaterial3: false, 16 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.orange), 17 | ); 18 | 19 | ThemeData yellowTheme = ThemeData.light().copyWith( 20 | // primaryColor: Colors.deepPurple, 21 | useMaterial3: false, 22 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.yellow), 23 | ); 24 | 25 | ThemeData greenTheme = ThemeData.light().copyWith( 26 | // primaryColor: Colors.deepPurple, 27 | useMaterial3: false, 28 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.green), 29 | ); 30 | 31 | ThemeData cyanTheme = ThemeData.light().copyWith( 32 | // primaryColor: Colors.deepPurple, 33 | useMaterial3: false, 34 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.cyan), 35 | ); 36 | 37 | ThemeData purpleTheme = ThemeData.light().copyWith( 38 | // primaryColor: Colors.deepPurple, 39 | useMaterial3: false, 40 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.purple), 41 | ); 42 | 43 | ThemeData deepPurpleTheme = ThemeData.light().copyWith( 44 | // primaryColor: Colors.deepPurple, 45 | useMaterial3: false, 46 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), 47 | ); 48 | 49 | ThemeData brownTheme = ThemeData.light().copyWith( 50 | // primaryColor: Colors.deepPurple, 51 | useMaterial3: false, 52 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.brown), 53 | ); 54 | 55 | ThemeData pinkTheme = ThemeData.light().copyWith( 56 | // primaryColor: Colors.deepPurple, 57 | useMaterial3: false, 58 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.pinkAccent), 59 | ); 60 | 61 | ThemeData darkTheme = ThemeData.dark().copyWith( 62 | useMaterial3: false, 63 | ); 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /notes/notes.txt: -------------------------------------------------------------------------------- 1 | flutter build web --no-tree-shake-icons 2 | copy build/web/* web/ -------------------------------------------------------------------------------- /screenshots/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/screenshots/1.jpg -------------------------------------------------------------------------------- /screenshots/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/screenshots/2.jpg -------------------------------------------------------------------------------- /screenshots/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/screenshots/3.jpg -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility in the flutter_test package. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | import 'package:moneynote/app.dart'; 11 | 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(const App()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | moneynote 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moneynote", 3 | "short_name": "moneynote", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "MoneyNote Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /web2/.last_build_id: -------------------------------------------------------------------------------- 1 | 0e730a2b102cdf47df544a6c12cdb24b -------------------------------------------------------------------------------- /web2/assets/AssetManifest.bin: -------------------------------------------------------------------------------- 1 | assets/logo.png  assetassets/logo.pngassets/logo.svg  assetassets/logo.svg -------------------------------------------------------------------------------- /web2/assets/AssetManifest.json: -------------------------------------------------------------------------------- 1 | {"assets/logo.png":["assets/logo.png"],"assets/logo.svg":["assets/logo.svg"]} -------------------------------------------------------------------------------- /web2/assets/FontManifest.json: -------------------------------------------------------------------------------- 1 | [{"family":"MaterialIcons","fonts":[{"asset":"fonts/MaterialIcons-Regular.otf"}]}] -------------------------------------------------------------------------------- /web2/assets/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/web2/assets/assets/logo.png -------------------------------------------------------------------------------- /web2/assets/fonts/MaterialIcons-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/web2/assets/fonts/MaterialIcons-Regular.otf -------------------------------------------------------------------------------- /web2/assets/packages/fluttertoast/assets/toastify.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Minified by jsDelivr using clean-css v4.2.3. 3 | * Original file: /npm/toastify-js@1.9.3/src/toastify.css 4 | * 5 | * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files 6 | */ 7 | /*! 8 | * Toastify js 1.9.3 9 | * https://github.com/apvarun/toastify-js 10 | * @license MIT licensed 11 | * 12 | * Copyright (C) 2018 Varun A P 13 | */ 14 | .toastify{padding:12px 20px;color:#fff;display:inline-block;box-shadow:0 3px 6px -1px rgba(0,0,0,.12),0 10px 36px -4px rgba(77,96,232,.3);background:-webkit-linear-gradient(315deg,#73a5ff,#5477f5);background:linear-gradient(135deg,#73a5ff,#5477f5);position:fixed;opacity:0;transition:all .4s cubic-bezier(.215,.61,.355,1);border-radius:2px;cursor:pointer;text-decoration:none;max-width:calc(50% - 20px);z-index:2147483647}.toastify.on{opacity:1}.toast-close{opacity:.4;padding:0 5px}.toastify-right{right:15px}.toastify-left{left:15px}.toastify-top{top:-150px}.toastify-bottom{bottom:-150px}.toastify-rounded{border-radius:25px}.toastify-avatar{width:1.5em;height:1.5em;margin:-7px 5px;border-radius:2px}.toastify-center{margin-left:auto;margin-right:auto;left:0;right:0;max-width:fit-content;max-width:-moz-fit-content}@media only screen and (max-width:360px){.toastify-left,.toastify-right{margin-left:auto;margin-right:auto;left:0;right:0;max-width:fit-content}} -------------------------------------------------------------------------------- /web2/canvaskit/canvaskit.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/web2/canvaskit/canvaskit.wasm -------------------------------------------------------------------------------- /web2/canvaskit/chromium/canvaskit.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/web2/canvaskit/chromium/canvaskit.wasm -------------------------------------------------------------------------------- /web2/canvaskit/skwasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/web2/canvaskit/skwasm.wasm -------------------------------------------------------------------------------- /web2/canvaskit/skwasm.worker.js: -------------------------------------------------------------------------------- 1 | "use strict";var Module={};var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";if(ENVIRONMENT_IS_NODE){var nodeWorkerThreads=require("worker_threads");var parentPort=nodeWorkerThreads.parentPort;parentPort.on("message",data=>onmessage({data:data}));var fs=require("fs");Object.assign(global,{self:global,require:require,Module:Module,location:{href:__filename},Worker:nodeWorkerThreads.Worker,importScripts:function(f){(0,eval)(fs.readFileSync(f,"utf8")+"//# sourceURL="+f)},postMessage:function(msg){parentPort.postMessage(msg)},performance:global.performance||{now:function(){return Date.now()}}})}var initializedJS=false;var pendingNotifiedProxyingQueues=[];function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");if(ENVIRONMENT_IS_NODE){fs.writeSync(2,text+"\n");return}console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;Module["instantiateWasm"]=(info,receiveInstance)=>{var module=Module["wasmModule"];Module["wasmModule"]=null;var instance=new WebAssembly.Instance(module,info);return receiveInstance(instance)};self.onunhandledrejection=e=>{throw e.reason??e};function handleMessage(e){try{if(e.data.cmd==="load"){let messageQueue=[];self.onmessage=e=>messageQueue.push(e);self.startWorker=instance=>{Module=instance;postMessage({"cmd":"loaded"});for(let msg of messageQueue){handleMessage(msg)}self.onmessage=handleMessage};Module["wasmModule"]=e.data.wasmModule;for(const handler of e.data.handlers){Module[handler]=function(){postMessage({cmd:"callHandler",handler:handler,args:[...arguments]})}}Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;if(typeof e.data.urlOrBlob=="string"){importScripts(e.data.urlOrBlob)}else{var objectUrl=URL.createObjectURL(e.data.urlOrBlob);importScripts(objectUrl);URL.revokeObjectURL(objectUrl)}skwasm(Module)}else if(e.data.cmd==="run"){Module["__emscripten_thread_init"](e.data.pthread_ptr,0,0,1);Module["establishStackSpace"]();Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInitTLS();if(!initializedJS){pendingNotifiedProxyingQueues.forEach(queue=>{Module["executeNotifiedProxyingQueue"](queue)});pendingNotifiedProxyingQueues=[];initializedJS=true}try{Module["invokeEntryPoint"](e.data.start_routine,e.data.arg)}catch(ex){if(ex!="unwind"){throw ex}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["__emscripten_thread_exit"](-1)}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processProxyingQueue"){if(initializedJS){Module["executeNotifiedProxyingQueue"](e.data.queue)}else{pendingNotifiedProxyingQueues.push(e.data.queue)}}else if(e.data.cmd){err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){if(Module["__emscripten_thread_crashed"]){Module["__emscripten_thread_crashed"]()}throw ex}}self.onmessage=handleMessage; 2 | -------------------------------------------------------------------------------- /web2/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/web2/favicon.png -------------------------------------------------------------------------------- /web2/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/web2/icons/Icon-192.png -------------------------------------------------------------------------------- /web2/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/web2/icons/Icon-512.png -------------------------------------------------------------------------------- /web2/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/web2/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web2/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getmoneynote/moneynote-flutter/b10d02ea283de3798a4972685a18ab6af7dc1929/web2/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web2/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "moneynote", 3 | "short_name": "moneynote", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "MoneyNote Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /web2/version.json: -------------------------------------------------------------------------------- 1 | {"app_name":"moneynote","version":"1.0.43","package_name":"moneynote"} --------------------------------------------------------------------------------