├── .fvmrc ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── actions │ └── setup │ │ └── action.yaml ├── dependabot.yaml └── workflows │ ├── checkout.yml │ └── test-report.yml ├── .gitignore ├── .metadata ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── HOWTO.md ├── LICENSE ├── Makefile ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── dev │ │ │ │ └── flutter │ │ │ │ └── template │ │ │ │ └── flutter_template_name │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-hdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-mdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-night-hdpi │ │ │ └── android12splash.png │ │ │ ├── drawable-night-mdpi │ │ │ └── android12splash.png │ │ │ ├── drawable-night-xhdpi │ │ │ └── android12splash.png │ │ │ ├── drawable-night-xxhdpi │ │ │ └── android12splash.png │ │ │ ├── drawable-night-xxxhdpi │ │ │ └── android12splash.png │ │ │ ├── drawable-v21 │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── drawable-xhdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-xxhdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-xxxhdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night-v31 │ │ │ └── styles.xml │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ ├── values-v31 │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets └── icons │ ├── icon-192-maskable.png │ ├── icon-192.png │ └── icon-500-transparent.png ├── build.yaml ├── config ├── development.json ├── production.json └── staging.json ├── dart_test.yaml ├── devtools_options.yaml ├── flutter_launcher_icons.yaml ├── flutter_native_splash.yaml ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── 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 │ │ ├── LaunchBackground.imageset │ │ │ ├── Contents.json │ │ │ └── background.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 ├── main.dart └── src │ ├── common │ ├── constant │ │ ├── assets.gen.dart │ │ ├── config.dart │ │ └── pubspec.yaml.g.dart │ ├── controller │ │ └── controller_observer.dart │ ├── database │ │ ├── database.dart │ │ ├── database.g.dart │ │ ├── ddl │ │ │ ├── characteristic.drift │ │ │ ├── kv.drift │ │ │ ├── log.drift │ │ │ └── settings.drift │ │ ├── platform │ │ │ ├── database_js.dart │ │ │ └── database_vm.dart │ │ └── queries.dart │ ├── localization │ │ ├── generated │ │ │ ├── intl │ │ │ │ ├── messages_all.dart │ │ │ │ └── messages_en.dart │ │ │ └── l10n.dart │ │ ├── intl_en.arb │ │ └── localization.dart │ ├── model │ │ ├── app_metadata.dart │ │ ├── dependencies.dart │ │ └── virtual_key_codes.dart │ ├── router │ │ ├── authentication_guard.dart │ │ ├── home_guard.dart │ │ ├── router_state_mixin.dart │ │ └── routes.dart │ ├── util │ │ ├── api_client.dart │ │ ├── app_zone.dart │ │ ├── color_util.dart │ │ ├── date_util.dart │ │ ├── error_util.dart │ │ ├── json_util.dart │ │ ├── keyboard_observer.dart │ │ ├── log_buffer.dart │ │ ├── middleware │ │ │ └── logger_mw.dart │ │ ├── placeholders.dart │ │ ├── platform │ │ │ ├── error_util_js.dart │ │ │ ├── error_util_vm.dart │ │ │ ├── keyboard_observer_interface.dart │ │ │ ├── keyboard_observer_js.dart │ │ │ ├── keyboard_observer_vm.dart │ │ │ └── keyboard_observer_windows.dart │ │ ├── screen_util.dart │ │ ├── stream_util.dart │ │ ├── timeouts.dart │ │ └── token_util.dart │ └── widget │ │ ├── adaptive_date_picker.dart │ │ ├── app.dart │ │ ├── app_error.dart │ │ ├── common_actions.dart │ │ ├── common_header.dart │ │ ├── history_button.dart │ │ ├── input_text_field.dart │ │ ├── not_found_screen.dart │ │ ├── outlined_text.dart │ │ ├── output_text_field.dart │ │ ├── scaffold_padding.dart │ │ ├── scroll_with_mouse_behavior.dart │ │ ├── sizer.dart │ │ ├── try_again_widget.dart │ │ └── window_scope.dart │ └── feature │ ├── account │ └── widget │ │ ├── profile_icon_button.dart │ │ └── profile_screen.dart │ ├── authentication │ ├── controller │ │ ├── authentication_controller.dart │ │ └── authentication_state.dart │ ├── data │ │ └── authentication_repository.dart │ ├── model │ │ ├── sign_in_data.dart │ │ └── user.dart │ └── widget │ │ ├── authentication_scope.dart │ │ ├── log_out_button.dart │ │ ├── signin_screen.dart │ │ └── signup_screen.dart │ ├── developer │ └── widget │ │ ├── developer_button.dart │ │ ├── developer_screen.dart │ │ └── logs_dialog.dart │ ├── home │ └── widget │ │ └── home_screen.dart │ ├── initialization │ ├── data │ │ ├── app_migrator.dart │ │ ├── initialization.dart │ │ ├── initialize_dependencies.dart │ │ └── platform │ │ │ ├── platform_initialization.dart │ │ │ ├── platform_initialization_js.dart │ │ │ └── platform_initialization_vm.dart │ └── widget │ │ └── initialization_splash_screen.dart │ └── settings │ └── widget │ ├── settings_dialog.dart │ ├── settings_icon_button.dart │ ├── settings_scope.dart │ └── settings_screen.dart ├── linux ├── .gitignore ├── CMakeLists.txt ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake ├── main.cc ├── my_application.cc ├── my_application.h └── runner │ ├── CMakeLists.txt │ ├── main.cc │ ├── my_application.cc │ └── my_application.h ├── macos ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── app_icon_1024.png │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_512.png │ │ │ └── app_icon_64.png │ ├── Base.lproj │ │ └── MainMenu.xib │ ├── Configs │ │ ├── AppInfo.xcconfig │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── Warnings.xcconfig │ ├── DebugProfile.entitlements │ ├── Info.plist │ ├── MainFlutterWindow.swift │ └── Release.entitlements └── RunnerTests │ └── RunnerTests.swift ├── packages └── ui │ ├── lib │ ├── shaders │ │ └── shimmer.frag │ ├── src │ │ ├── components │ │ │ ├── charts │ │ │ │ ├── charts.dart │ │ │ │ └── pie_chart.dart │ │ │ ├── components.dart │ │ │ ├── layout │ │ │ │ ├── layout.dart │ │ │ │ └── popup.dart │ │ │ └── placeholders │ │ │ │ ├── form_placeholder.dart │ │ │ │ ├── placeholders.dart │ │ │ │ ├── radial_progress_indicator.dart │ │ │ │ ├── shimmer.dart │ │ │ │ └── text_placeholder.dart │ │ └── theme │ │ │ ├── extensions │ │ │ └── colors.dart │ │ │ └── theme.dart │ └── ui.dart │ └── pubspec.yaml ├── pubspec.lock ├── pubspec.yaml ├── test ├── src │ └── util │ │ └── pump_screen.dart ├── unit_test.dart └── widget_test.dart ├── tool └── dart │ └── rename_project.dart ├── web ├── AppIcon~ios-marketing.png ├── android-chrome-192x192.png ├── android-chrome-384x384.png ├── apple-touch-icon.png ├── browserconfig.xml ├── drift_worker.js ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── favicon.png ├── icon-192-maskable.png ├── icon-192.png ├── icon-2048.png ├── icon-500-transparent.png ├── icon-512-maskable.png ├── icon-512.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── index.html ├── manifest.json ├── mstile-150x150.png ├── mstile-310x150.png ├── mstile-310x310.png ├── mstile-70x70.png ├── play_store_512.png ├── safari-pinned-tab.svg ├── splash │ └── img │ │ ├── dark-1x.png │ │ ├── dark-2x.png │ │ ├── dark-3x.png │ │ ├── dark-4x.png │ │ ├── light-1x.png │ │ ├── light-2x.png │ │ ├── light-3x.png │ │ └── light-4x.png └── sqlite3.wasm └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter ├── CMakeLists.txt ├── generated_plugin_registrant.cc ├── generated_plugin_registrant.h └── generated_plugins.cmake └── runner ├── CMakeLists.txt ├── Runner.rc ├── flutter_window.cpp ├── flutter_window.h ├── main.cpp ├── resource.h ├── resources └── app_icon.ico ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h /.fvmrc: -------------------------------------------------------------------------------- 1 | { 2 | "flutter": "3.29.0" 3 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | #github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | #patreon: plugfox 5 | #open_collective: # Replace with a single Open Collective username 6 | #ko_fi: # Replace with a single Ko-fi username 7 | #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | #liberapay: # Replace with a single Liberapay username 10 | #issuehunt: # Replace with a single IssueHunt username 11 | #otechie: # Replace with a single Otechie username 12 | #lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | #custom: ['https://www.buymeacoffee.com/plugfox', 'https://boosty.to/plugfox'] 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | 28 | - OS: [e.g. iOS] 29 | - Browser [e.g. chrome, safari] 30 | - Version [e.g. 22] 31 | 32 | **Smartphone (please complete the following information):** 33 | 34 | - Device: [e.g. iPhone6] 35 | - OS: [e.g. iOS8.1] 36 | - Browser [e.g. stock browser, safari] 37 | - Version [e.g. 22] 38 | 39 | **Additional context** 40 | Add any other context about the problem here. 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/actions/setup/action.yaml: -------------------------------------------------------------------------------- 1 | name: Setup 2 | description: Sets up the Flutter environment 3 | 4 | inputs: 5 | flutter-version: 6 | description: 'The version of Flutter to use' 7 | required: false 8 | default: '3.29.0' 9 | pub-cache: 10 | description: 'The name of the pub cache variable' 11 | required: false 12 | default: app 13 | 14 | runs: 15 | using: composite 16 | steps: 17 | - name: 📦 Checkout the repo 18 | uses: actions/checkout@v4 19 | 20 | - name: 🔢 Set up version from tags 21 | id: set-version 22 | if: startsWith(github.ref, 'refs/tags') 23 | shell: bash 24 | run: | 25 | BASE_VERSION="${GITHUB_REF#refs/tags/v}" 26 | UNIXTIME=$(date +%s) 27 | VERSION="${BASE_VERSION}+${UNIXTIME}" 28 | echo "VERSION=$VERSION" >> $GITHUB_ENV 29 | sed -i "s/^version: .*/version: ${VERSION}/" pubspec.yaml 30 | echo "Version set to $VERSION" 31 | 32 | - name: 🚂 Setup Flutter 33 | uses: subosito/flutter-action@v2 34 | with: 35 | flutter-version: '${{ inputs.flutter-version }}' 36 | channel: "stable" 37 | 38 | - name: 📤 Restore Pub modules 39 | id: cache-pub-restore 40 | uses: actions/cache/restore@v4 41 | with: 42 | path: | 43 | /home/runner/.pub-cache 44 | key: ${{ runner.os }}-pub-${{ inputs.pub-cache }}-${{ hashFiles('pubspec.lock') }} 45 | 46 | - name: 👷 Install Dependencies 47 | shell: bash 48 | run: | 49 | echo /home/runner/.pub-cache/bin >> $GITHUB_PATH 50 | flutter config --no-cli-animations --no-analytics 51 | flutter pub get 52 | 53 | - name: ⏲️ Run build runner 54 | shell: bash 55 | run: | 56 | dart run build_runner build --delete-conflicting-outputs --release 57 | 58 | - name: 📥 Save Pub modules 59 | id: cache-pub-save 60 | if: steps.cache-pub-restore.outputs.cache-hit != 'true' 61 | uses: actions/cache/save@v4 62 | with: 63 | path: | 64 | /home/runner/.pub-cache 65 | key: ${{ steps.cache-pub-restore.outputs.cache-primary-key }} 66 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | enable-beta-ecosystems: true 3 | updates: 4 | - directory: "/" 5 | open-pull-requests-limit: 5 6 | package-ecosystem: "pub" 7 | rebase-strategy: auto 8 | schedule: 9 | interval: "monthly" 10 | # timezone: "UTC" -------------------------------------------------------------------------------- /.github/workflows/test-report.yml: -------------------------------------------------------------------------------- 1 | name: "Test Report" 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["Checkout"] # runs after "Checkout" workflow 6 | types: 7 | - completed 8 | 9 | permissions: 10 | contents: read 11 | actions: read 12 | checks: write 13 | 14 | jobs: 15 | report: 16 | name: "🚛 Test report" 17 | runs-on: ubuntu-latest 18 | timeout-minutes: 10 19 | steps: 20 | - name: Test report 21 | uses: dorny/test-reporter@v1 22 | with: 23 | artifact: test-results 24 | name: Test Report 25 | path: "**/tests.json" 26 | reporter: flutter-json 27 | fail-on-error: false 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .build/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | .swiftpm/ 13 | migrate_working_dir/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | **/ios/Flutter/.last_build_id 29 | .dart_tool/ 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | .pub-cache/ 33 | .pub/ 34 | /build/ 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Android Studio will place build artifacts here 43 | /android/app/debug 44 | /android/app/profile 45 | /android/app/release 46 | /android/app/.cxx 47 | 48 | # Files generated by flutter test 49 | **/coverage/ 50 | lcov.info 51 | reports/ 52 | 53 | # FVM Version Cache 54 | .fvm/ -------------------------------------------------------------------------------- /.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: "17025dd88227cd9532c33fa78f5250d548d87e9a" 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: 17025dd88227cd9532c33fa78f5250d548d87e9a 17 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 18 | - platform: android 19 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 20 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 21 | - platform: ios 22 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 23 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 24 | - platform: linux 25 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 26 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 27 | - platform: macos 28 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 29 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 30 | - platform: web 31 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 32 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 33 | - platform: windows 34 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 35 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "Dart-Code.dart-code", 4 | "Dart-Code.flutter" 5 | ], 6 | "unwantedRecommendations": [] 7 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Run DEV App (Debug)", 6 | "type": "dart", 7 | "program": "lib/main.dart", 8 | "request": "launch", 9 | "cwd": "${workspaceFolder}", 10 | "args": [ 11 | "--dart-define-from-file=config/development.json", 12 | ], 13 | "env": {} 14 | }, 15 | { 16 | "name": "Run DEV App (Debug, Drop database)", 17 | "type": "dart", 18 | "program": "lib/main.dart", 19 | "request": "launch", 20 | "cwd": "${workspaceFolder}", 21 | "args": [ 22 | "--dart-define-from-file=config/development.json", 23 | "--dart-define=DROP_DATABASE=true" 24 | ], 25 | "env": {} 26 | }, 27 | { 28 | "name": "Run DEV App (Debug, Memmory database)", 29 | "type": "dart", 30 | "program": "lib/main.dart", 31 | "request": "launch", 32 | "cwd": "${workspaceFolder}", 33 | "args": [ 34 | "--dart-define-from-file=config/development.json", 35 | "--dart-define=IN_MEMORY_DATABASE=true" 36 | ], 37 | "env": {} 38 | }, 39 | { 40 | "name": "Run DEV Web Server (Debug)", 41 | "type": "dart", 42 | "program": "lib/main.dart", 43 | "request": "launch", 44 | "cwd": "${workspaceFolder}", 45 | "args": [ 46 | "--dart-define-from-file=config/development.json", 47 | "--device-id=web-server", 48 | ], 49 | "env": {} 50 | } 51 | ] 52 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dart.lineLength": 120, 3 | "editor.rulers": [ 4 | 120 5 | ], 6 | "[dart]": { 7 | "editor.insertSpaces": true, 8 | "editor.tabSize": 2, 9 | "editor.suggest.snippetsPreventQuickSuggestions": false, 10 | "editor.suggestSelection": "first", 11 | "editor.tabCompletion": "onlySnippets", 12 | "editor.wordBasedSuggestions": "off", 13 | "editor.selectionHighlight": false, 14 | "editor.defaultFormatter": "Dart-Code.dart-code", 15 | "editor.formatOnSave": true, 16 | "editor.formatOnType": true, 17 | "editor.formatOnPaste": true, 18 | "editor.codeActionsOnSave": { 19 | "source.fixAll": "explicit", 20 | "source.organizeImports": "explicit" 21 | }, 22 | "editor.quickSuggestions": { 23 | "comments": "on", 24 | "strings": "on", 25 | "other": "on" 26 | }, 27 | "editor.links": true 28 | }, 29 | "dart.doNotFormat": [ 30 | "**.g.dart", 31 | "**.gql.dart", 32 | "**.freezed.dart", 33 | "**.config.dart", 34 | "**.mocks.dart", 35 | "**.gen.dart", 36 | "**.pb.dart", 37 | "**.pbenum.dart", 38 | "**.pbjson.dart", 39 | "**/generated/**" 40 | ], 41 | "dart.additionalAnalyzerFileExtensions": [ 42 | "drift" 43 | ], 44 | "search.exclude": { 45 | "**/.fvm": true, 46 | ".dart_tool": true, 47 | "coverage": true, 48 | "build": true 49 | }, 50 | "files.watcherExclude": { 51 | "**/.fvm": true, 52 | ".dart_tool": true, 53 | "coverage": true, 54 | "build": true 55 | }, 56 | "debug.openDebug": "openOnDebugBreak", 57 | "explorer.fileNesting.enabled": true, 58 | "explorer.fileNesting.expand": false, 59 | "explorer.fileNesting.patterns": { 60 | "pubspec.yaml": ".flutter-plugins, .packages, .dart_tool, .flutter-plugins-dependencies, .metadata, .packages, pubspec.lock, build.yaml, analysis_options.yaml, all_lint_rules.yaml, dart*.yaml, flutter*.yaml, icons_launcher.yaml, l10n.yaml, melos.yaml, pubspec_overrides.yaml, devtools_options.yaml", 61 | ".gitignore": ".gitattributes, .gitmodules, .gitmessage, .mailmap, .git-blame*", 62 | "readme.*": "authors, backers.md, changelog*, citation*, code_of_conduct.md, codeowners, contributing.md, contributors, copying, credits, governance.md, history.md, license*, maintainers, readme*, security.md, sponsors.md, how_to.md, howto.md", 63 | "*.dart": "i_$(capture).dart, $(capture).g.dart, $(capture).gr.dart, $(capture).freezed.dart, $(capture).config.dart, $(capture).chopper.dart, $(capture).drift, $(capture)_io.dart, $(capture)_web.dart, $(capture)_shared.dart, $(capture)_stub.dart", 64 | "*.graphql": "$(capture).gql.dart, $(capture).*.gql.dart", 65 | "firebase.json": " .firebaserc", 66 | "main.dart": "runner_io.dart, runner_shared.dart, runner_stub.dart, runner_web.dart, runner_vm.dart, runner_js.dart, runner_wasm.dart, runner_mobile.dart, runner_desktop.dart" 67 | }, 68 | "files.associations": { 69 | "*.drift": "sql" 70 | }, 71 | "dart.flutterSdkPath": ".fvm/versions/3.29.0" 72 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Unreleased 2 | 3 | - **ADDED**: 4 | - **CHANGED**: 5 | - **DEPRECATED**: 6 | - **REMOVED**: 7 | - **FIXED**: 8 | - **SECURITY**: 9 | - **REFACTOR**: 10 | - **DOCS**: 11 | 12 | ## 0.0.1 13 | 14 | - **ADDED**: 15 | - **CHANGED**: 16 | - **DEPRECATED**: 17 | - **REMOVED**: 18 | - **FIXED**: 19 | - **SECURITY**: 20 | - **REFACTOR**: 21 | - **DOCS**: 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/CONTRIBUTING.md -------------------------------------------------------------------------------- /HOWTO.md: -------------------------------------------------------------------------------- 1 | # How to... 2 | 3 | ## Initialize the Firebase 4 | 5 | ```bash 6 | npm install -g firebase-tools 7 | firebase login 8 | firebase init 9 | dart pub global activate flutterfire_cli 10 | flutterfire configure \ 11 | -i tld.domain.app \ 12 | -m tld.domain.app \ 13 | -a tld.domain.app \ 14 | -p project \ 15 | -e email@gmail.com \ 16 | -o lib/src/common/constant/firebase_options.g.dart 17 | ``` 18 | 19 | ## Drop the Flutter cache 20 | 21 | Clear the dart cache and flutter cache. 22 | 23 | ```bash 24 | nohup bash -c 'rm -rf ~/.pub-cache $PUB_CACHE \ 25 | && cd $(dirname -- $(which flutter)) \ 26 | && git clean -fdx' > /dev/null 2>&1 & 27 | ``` 28 | 29 | macOS: 30 | 31 | ```zsh 32 | rm -rf ~/.pub-cache ~/.dart ~/.dartServer $PUB_CACHE 33 | ``` 34 | 35 | Windows: 36 | 37 | ```cmd 38 | del C:\Users\\AppData\Local\.dartServer 39 | ``` 40 | 41 | And set up it again. 42 | 43 | ```bash 44 | yes | flutter doctor --android-licenses 45 | ``` 46 | 47 | ## Update platform code 48 | 49 | ```bash 50 | flutter create -t app --project-name "flutter_template_name" --org "dev.flutter.template" --description "flutter_template_description" --platform=android,ios,macos,windows,linux,web --overwrite . 51 | ``` 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/LICENSE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flutter template by Plague Fox 2 | 3 | ## Get started 4 | 5 | Replace project name, description and organization: 6 | 7 | ```bash 8 | dart run tool/dart/rename_project.dart --name="project" --organization="tld.domain" --description="My project description" 9 | ``` 10 | -------------------------------------------------------------------------------- /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/to/reference-keystore 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. 5 | id "dev.flutter.flutter-gradle-plugin" 6 | } 7 | 8 | android { 9 | namespace = "dev.flutter.template.flutter_template_name" 10 | compileSdk = flutter.compileSdkVersion 11 | ndkVersion = flutter.ndkVersion 12 | 13 | compileOptions { 14 | sourceCompatibility = JavaVersion.VERSION_1_8 15 | targetCompatibility = JavaVersion.VERSION_1_8 16 | } 17 | 18 | kotlinOptions { 19 | jvmTarget = JavaVersion.VERSION_1_8 20 | } 21 | 22 | defaultConfig { 23 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 24 | applicationId = "dev.flutter.template.flutter_template_name" 25 | // You can update the following values to match your application needs. 26 | // For more information, see: https://flutter.dev/to/review-gradle-config. 27 | minSdk = flutter.minSdkVersion 28 | targetSdk = flutter.targetSdkVersion 29 | versionCode = flutter.versionCode 30 | versionName = flutter.versionName 31 | } 32 | 33 | buildTypes { 34 | release { 35 | // TODO: Add your own signing config for the release build. 36 | // Signing with the debug keys for now, so `flutter run --release` works. 37 | signingConfig = signingConfigs.debug 38 | } 39 | } 40 | } 41 | 42 | flutter { 43 | source = "../.." 44 | } 45 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/dev/flutter/template/flutter_template_name/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package dev.flutter.template.flutter_template_name 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() 6 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/android/app/src/main/res/drawable-hdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/android/app/src/main/res/drawable-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/android/app/src/main/res/drawable-mdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/android/app/src/main/res/drawable-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-hdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/android/app/src/main/res/drawable-night-hdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-mdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/android/app/src/main/res/drawable-night-mdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/android/app/src/main/res/drawable-night-xhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/android/app/src/main/res/drawable-v21/background.png -------------------------------------------------------------------------------- /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-xhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/android/app/src/main/res/drawable-xhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/android/app/src/main/res/drawable-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/android/app/src/main/res/drawable-xxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/android/app/src/main/res/drawable-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/android/app/src/main/res/drawable-xxxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/android/app/src/main/res/drawable-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/android/app/src/main/res/drawable/background.png -------------------------------------------------------------------------------- /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/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night-v31/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-v31/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /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 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = "../build" 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(":app") 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError 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-8.3-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 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version "8.1.0" apply false 22 | id "org.jetbrains.kotlin.android" version "1.8.22" apply false 23 | } 24 | 25 | include ":app" 26 | -------------------------------------------------------------------------------- /assets/icons/icon-192-maskable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/assets/icons/icon-192-maskable.png -------------------------------------------------------------------------------- /assets/icons/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/assets/icons/icon-192.png -------------------------------------------------------------------------------- /assets/icons/icon-500-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/assets/icons/icon-500-transparent.png -------------------------------------------------------------------------------- /build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | sources: 4 | - $package$ 5 | - pubspec.yaml 6 | - lib/** 7 | - test/** 8 | builders: 9 | pubspec_generator: 10 | options: 11 | output: lib/src/common/constant/pubspec.yaml.g.dart 12 | drift_dev: 13 | generate_for: 14 | include: 15 | - lib/src/common/database/** 16 | options: 17 | data_class_to_companions: true 18 | mutable_classes: false 19 | sqlite: 20 | version: "3.41" 21 | -------------------------------------------------------------------------------- /config/development.json: -------------------------------------------------------------------------------- 1 | { 2 | "ENVIRONMENT": "development", 3 | "MAX_LAYOUT_WIDTH": 768 4 | } 5 | -------------------------------------------------------------------------------- /config/production.json: -------------------------------------------------------------------------------- 1 | { 2 | "ENVIRONMENT": "production", 3 | "MAX_LAYOUT_WIDTH": 768 4 | } 5 | -------------------------------------------------------------------------------- /config/staging.json: -------------------------------------------------------------------------------- 1 | { 2 | "ENVIRONMENT": "staging", 3 | "MAX_LAYOUT_WIDTH": 768 4 | } 5 | -------------------------------------------------------------------------------- /dart_test.yaml: -------------------------------------------------------------------------------- 1 | timeout: 1x 2 | 3 | platforms: 4 | - vm 5 | 6 | file_reporters: 7 | json: reports/tests.json 8 | 9 | tags: 10 | model: 11 | timeout: 1x 12 | data: 13 | timeout: 1x 14 | controller: 15 | timeout: 1x 16 | widget: 17 | timeout: 1x 18 | 19 | -------------------------------------------------------------------------------- /devtools_options.yaml: -------------------------------------------------------------------------------- 1 | extensions: 2 | -------------------------------------------------------------------------------- /flutter_launcher_icons.yaml: -------------------------------------------------------------------------------- 1 | flutter_icons: 2 | image_path: "web/icon-512-maskable.png" 3 | android: true 4 | image_path_android: "web/play_store_512.png" 5 | adaptive_icon_background: "#37474f" 6 | min_sdk_android: 21 # android min sdk min:16, default 21 7 | ios: true 8 | image_path_ios: "web/AppIcon~ios-marketing.png" 9 | remove_alpha_ios: true 10 | # https://icon.kitchen 11 | # https://realfavicongenerator.net 12 | web: 13 | generate: false 14 | # image_path: "web/icon-512-maskable.png" 15 | # background_color: "#37474f" 16 | # theme_color: "#37474f" 17 | windows: 18 | generate: true 19 | image_path: "web/icon-512-maskable.png" 20 | icon_size: 256 # min:48, max:256, default: 48 21 | macos: 22 | generate: true 23 | image_path: "web/AppIcon~ios-marketing.png" 24 | -------------------------------------------------------------------------------- /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 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '12.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | target 'RunnerTests' do 36 | inherit! :search_paths 37 | end 38 | end 39 | 40 | post_install do |installer| 41 | installer.pods_project.targets.each do |target| 42 | flutter_additional_ios_build_settings(target) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_native_splash (2.4.3): 4 | - Flutter 5 | - integration_test (0.0.1): 6 | - Flutter 7 | - path_provider_foundation (0.0.1): 8 | - Flutter 9 | - FlutterMacOS 10 | - shared_preferences_foundation (0.0.1): 11 | - Flutter 12 | - FlutterMacOS 13 | - sqlite3 (3.49.0): 14 | - sqlite3/common (= 3.49.0) 15 | - sqlite3/common (3.49.0) 16 | - sqlite3/dbstatvtab (3.49.0): 17 | - sqlite3/common 18 | - sqlite3/fts5 (3.49.0): 19 | - sqlite3/common 20 | - sqlite3/perf-threadsafe (3.49.0): 21 | - sqlite3/common 22 | - sqlite3/rtree (3.49.0): 23 | - sqlite3/common 24 | - sqlite3_flutter_libs (0.0.1): 25 | - Flutter 26 | - FlutterMacOS 27 | - sqlite3 (~> 3.49.0) 28 | - sqlite3/dbstatvtab 29 | - sqlite3/fts5 30 | - sqlite3/perf-threadsafe 31 | - sqlite3/rtree 32 | - url_launcher_ios (0.0.1): 33 | - Flutter 34 | 35 | DEPENDENCIES: 36 | - Flutter (from `Flutter`) 37 | - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) 38 | - integration_test (from `.symlinks/plugins/integration_test/ios`) 39 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) 40 | - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) 41 | - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`) 42 | - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) 43 | 44 | SPEC REPOS: 45 | trunk: 46 | - sqlite3 47 | 48 | EXTERNAL SOURCES: 49 | Flutter: 50 | :path: Flutter 51 | flutter_native_splash: 52 | :path: ".symlinks/plugins/flutter_native_splash/ios" 53 | integration_test: 54 | :path: ".symlinks/plugins/integration_test/ios" 55 | path_provider_foundation: 56 | :path: ".symlinks/plugins/path_provider_foundation/darwin" 57 | shared_preferences_foundation: 58 | :path: ".symlinks/plugins/shared_preferences_foundation/darwin" 59 | sqlite3_flutter_libs: 60 | :path: ".symlinks/plugins/sqlite3_flutter_libs/darwin" 61 | url_launcher_ios: 62 | :path: ".symlinks/plugins/url_launcher_ios/ios" 63 | 64 | SPEC CHECKSUMS: 65 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 66 | flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29 67 | integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 68 | path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 69 | shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 70 | sqlite3: 4922312598b67e1825c6a6c821296dcbf6783046 71 | sqlite3_flutter_libs: 069c435986dd4b63461aecd68f4b30be4a9e9daa 72 | url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe 73 | 74 | PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 75 | 76 | COCOAPODS: 1.16.2 77 | -------------------------------------------------------------------------------- /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 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /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 Flutter 2 | import UIKit 3 | 4 | @main 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/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/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/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/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/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/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/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/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/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/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/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/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/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/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/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/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/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/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/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/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/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/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/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/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/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/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/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/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/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/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/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/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/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/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/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/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/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/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/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/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/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "background.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.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/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/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 | Flutter Template Name 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | flutter_template_name 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/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:flutter_template_name/src/common/util/app_zone.dart'; 5 | import 'package:flutter_template_name/src/common/util/error_util.dart'; 6 | import 'package:flutter_template_name/src/common/widget/app.dart'; 7 | import 'package:flutter_template_name/src/common/widget/app_error.dart' deferred as app_error; 8 | import 'package:flutter_template_name/src/feature/initialization/data/initialization.dart' deferred as initialization; 9 | import 'package:flutter_template_name/src/feature/settings/widget/settings_scope.dart'; 10 | import 'package:octopus/octopus.dart'; 11 | import 'package:platform_info/platform_info.dart'; 12 | 13 | void main() => appZone(() async { 14 | // Splash screen 15 | final initializationProgress = ValueNotifier<({int progress, String message})>((progress: 0, message: '')); 16 | /* runApp(SplashScreen(progress: initializationProgress)); */ 17 | await initialization.loadLibrary(); 18 | initialization 19 | .$initializeApp( 20 | onProgress: (progress, message) => initializationProgress.value = (progress: progress, message: message), 21 | onSuccess: 22 | (dependencies) => runApp( 23 | dependencies.inject( 24 | child: SettingsScope( 25 | child: NoAnimationScope(noAnimation: platform.js || platform.desktop, child: const App()), 26 | ), 27 | ), 28 | ), 29 | onError: (error, stackTrace) async { 30 | await app_error.loadLibrary(); 31 | runApp(app_error.AppError(error: error)); 32 | ErrorUtil.logError(error, stackTrace).ignore(); 33 | }, 34 | ) 35 | .ignore(); 36 | }); 37 | -------------------------------------------------------------------------------- /lib/src/common/controller/controller_observer.dart: -------------------------------------------------------------------------------- 1 | import 'package:control/control.dart'; 2 | import 'package:flutter_template_name/src/common/util/error_util.dart'; 3 | import 'package:l/l.dart'; 4 | 5 | /// Observer for [Controller], react to changes in any controller. 6 | final class ControllerObserver implements IControllerObserver { 7 | const ControllerObserver(); 8 | 9 | @override 10 | void onCreate(Controller controller) { 11 | l.v6('Controller | ${controller.name}.new'); 12 | } 13 | 14 | @override 15 | void onDispose(Controller controller) { 16 | l.v5('Controller | ${controller.name}.dispose'); 17 | } 18 | 19 | @override 20 | void onHandler(HandlerContext context) { 21 | final stopwatch = Stopwatch()..start(); 22 | l.d('Controller | ${context.controller.name}.${context.name}', context.meta); 23 | context.done.whenComplete(() { 24 | stopwatch.stop(); 25 | l.d( 26 | 'Controller | ${context.controller.name}.${context.name} | ' 27 | 'duration: ${stopwatch.elapsed}', 28 | context.meta, 29 | ); 30 | }); 31 | } 32 | 33 | @override 34 | void onStateChanged(StateController controller, S prevState, S nextState) { 35 | final context = Controller.context; 36 | if (context == null) { 37 | // State change occurred outside of the handler 38 | l.d('StateController | ${controller.name} | $prevState -> $nextState'); 39 | } else { 40 | // State change occurred inside the handler 41 | l.d('StateController | ${controller.name}.${context.name} | $prevState -> $nextState', context.meta); 42 | } 43 | } 44 | 45 | @override 46 | void onError(Controller controller, Object error, StackTrace stackTrace) { 47 | final context = Controller.context; 48 | if (context == null) { 49 | // Error occurred outside of the handler 50 | l.w('Controller | ${controller.name} | $error', stackTrace); 51 | } else { 52 | // Error occurred inside the handler 53 | l.w('Controller | ${controller.name}.${context.name} | $error', stackTrace, context.meta); 54 | } 55 | ErrorUtil.logError(error, stackTrace); 56 | } 57 | } 58 | 59 | // Example of any event 60 | // Future event({ 61 | // Map? meta, 62 | // void Function(HandlerContext context)? out, 63 | // }) => 64 | // handle( 65 | // () async { 66 | // final stopwatch = Stopwatch()..start(); 67 | // try { 68 | // setState(false); 69 | // await Future.delayed(Duration.zero); 70 | // out?.call(Controller.context!); 71 | // setState(true); 72 | // Controller.context?.meta['duration'] = stopwatch.elapsed; 73 | // } finally { 74 | // stopwatch.stop(); 75 | // } 76 | // }, 77 | // name: 'event', 78 | // meta: { 79 | // ...?meta, 80 | // 'started_at': DateTime.now(), 81 | // }, 82 | // ); 83 | -------------------------------------------------------------------------------- /lib/src/common/database/ddl/characteristic.drift: -------------------------------------------------------------------------------- 1 | -- Characteristics table 2 | CREATE TABLE IF NOT EXISTS characteristic_tbl ( 3 | -- req Type 4 | type TEXT NOT NULL CHECK(length(type) > 0 AND length(type) <= 255), 5 | 6 | -- req ID 7 | id INTEGER NOT NULL, 8 | 9 | -- JSON data 10 | data TEXT NOT NULL CHECK(length(data) > 2 AND json_valid(data)), 11 | 12 | -- Created date (unixtime in seconds) 13 | meta_created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')), 14 | -- Updated date (unixtime in seconds) 15 | meta_updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) CHECK(meta_updated_at >= meta_created_at), 16 | 17 | -- Composite primary key 18 | PRIMARY KEY (type, id) 19 | ) STRICT; 20 | 21 | -- Indexes 22 | CREATE INDEX IF NOT EXISTS characteristic_meta_created_at_idx ON characteristic_tbl (meta_created_at); 23 | CREATE INDEX IF NOT EXISTS characteristic_meta_updated_at_idx ON characteristic_tbl (meta_updated_at); 24 | 25 | -- Triggers 26 | CREATE TRIGGER IF NOT EXISTS characteristic_meta_updated_at_trig AFTER UPDATE ON characteristic_tbl 27 | BEGIN 28 | UPDATE characteristic_tbl SET meta_updated_at = strftime('%s', 'now') WHERE type = NEW.type AND id = NEW.id; 29 | END; 30 | -------------------------------------------------------------------------------- /lib/src/common/database/ddl/kv.drift: -------------------------------------------------------------------------------- 1 | -- Key-Value table 2 | CREATE TABLE IF NOT EXISTS kv_tbl ( 3 | -- req Key 4 | k TEXT NOT NULL PRIMARY KEY, 5 | -- string 6 | vstring TEXT, 7 | -- Integer 8 | vint INTEGER, 9 | -- Float 10 | vdouble REAL, 11 | -- Boolean 12 | vbool INTEGER, 13 | -- Binary 14 | --vblob BLOB, 15 | -- req Created date (unixtime in seconds) 16 | meta_created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')), 17 | -- req Updated date (unixtime in seconds) 18 | meta_updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) CHECK(meta_updated_at >= meta_created_at) 19 | ) STRICT WITHOUT ROWID; 20 | 21 | -- Indexes 22 | CREATE INDEX IF NOT EXISTS kv_meta_created_at_idx ON kv_tbl (meta_created_at); 23 | CREATE INDEX IF NOT EXISTS kv_meta_updated_at_idx ON kv_tbl (meta_updated_at); 24 | 25 | CREATE TRIGGER IF NOT EXISTS kv_meta_updated_at_trig AFTER UPDATE ON kv_tbl 26 | BEGIN 27 | UPDATE kv_tbl SET meta_updated_at = strftime('%s', 'now') WHERE k = NEW.k; 28 | END; 29 | -------------------------------------------------------------------------------- /lib/src/common/database/ddl/log.drift: -------------------------------------------------------------------------------- 1 | -- PRAGMA synchronous = OFF; 2 | -- PRAGMA journal_mode = MEMORY; 3 | 4 | -- Logs table 5 | CREATE TABLE IF NOT EXISTS log_tbl ( 6 | -- req Unique identifier of the log 7 | id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 8 | 9 | -- Time is the timestamp (in seconds) of the log message 10 | time INTEGER NOT NULL DEFAULT (strftime('%s', 'now')), 11 | 12 | -- Level is the severity level (a value between 0 and 6) 13 | level INTEGER NOT NULL, 14 | 15 | -- req Message is the log message or error associated with this log event 16 | message TEXT NOT NULL, 17 | 18 | -- StackTrace a stack trace associated with this log event 19 | stack TEXT 20 | ) STRICT; -- WITHOUT ROWID; 21 | 22 | -- Indexes 23 | CREATE INDEX IF NOT EXISTS log_time_idx ON log_tbl (time); 24 | CREATE INDEX IF NOT EXISTS log_level_idx ON log_tbl (level); 25 | -- CREATE INDEX IF NOT EXISTS log_time_level_idx ON log_tbl (time, level); 26 | 27 | 28 | -- Search table 29 | CREATE TABLE IF NOT EXISTS log_prefix_tbl ( 30 | -- req Prefix (first 3 chars of word, lowercased) 31 | prefix TEXT NOT NULL, -- CHECK(length(prefix) = 3) 32 | 33 | -- req Unique identifier 34 | log_id INTEGER NOT NULL, 35 | 36 | -- req Word (3 or more chars, lowercased) 37 | word TEXT NOT NULL, 38 | 39 | -- req Word's length 40 | len INTEGER NOT NULL, 41 | 42 | -- Composite primary key 43 | PRIMARY KEY (prefix, log_id, word), 44 | 45 | -- Foreign keys 46 | FOREIGN KEY (log_id) 47 | REFERENCES log_tbl (id) 48 | ON UPDATE CASCADE 49 | ON DELETE CASCADE 50 | ) STRICT WITHOUT ROWID; 51 | 52 | -- Indexes 53 | CREATE INDEX IF NOT EXISTS log_prefix_prefix_idx ON log_prefix_tbl (prefix); 54 | CREATE INDEX IF NOT EXISTS log_prefix_log_id_idx ON log_prefix_tbl (log_id); 55 | CREATE INDEX IF NOT EXISTS log_prefix_len_idx ON log_prefix_tbl (len); 56 | 57 | -------------------------------------------------------------------------------- /lib/src/common/database/ddl/settings.drift: -------------------------------------------------------------------------------- 1 | -- Settings table 2 | CREATE TABLE IF NOT EXISTS settings_tbl ( 3 | -- User ID 4 | user_id TEXT NOT NULL PRIMARY KEY, 5 | 6 | -- JSON data 7 | json_data TEXT NOT NULL CHECK(length(json_data) > 2 AND json_valid(json_data)), 8 | 9 | -- Description 10 | memo TEXT, 11 | 12 | -- Created date (unixtime in seconds) 13 | meta_created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')), 14 | -- Updated date (unixtime in seconds) 15 | meta_updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) CHECK(meta_updated_at >= meta_created_at) 16 | ) STRICT; 17 | 18 | -- Triggers 19 | CREATE TRIGGER IF NOT EXISTS settings_meta_updated_at_trig AFTER UPDATE ON settings_tbl 20 | BEGIN 21 | UPDATE settings_tbl SET meta_updated_at = strftime('%s', 'now') WHERE user_id = NEW.user_id; 22 | END; -------------------------------------------------------------------------------- /lib/src/common/database/platform/database_js.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_web_libraries_in_flutter 2 | 3 | import 'package:drift/drift.dart'; 4 | import 'package:drift/wasm.dart' as wasm; 5 | import 'package:flutter_template_name/src/common/constant/config.dart'; 6 | import 'package:l/l.dart'; 7 | import 'package:meta/meta.dart'; 8 | import 'package:web/web.dart' as web; 9 | 10 | /* 11 | IdbFactory.supported => WebDatabase.withStorage(await DriftWebStorage.indexedDbIfSupported(name)); 12 | https://github.com/flutter/flutter/issues/44937 13 | */ 14 | @internal 15 | Future $createQueryExecutor({ 16 | String? path, 17 | bool logStatements = false, 18 | bool dropDatabase = false, 19 | bool memoryDatabase = false, 20 | }) async { 21 | // https://drift.simonbinder.eu/web 22 | /* 23 | if (memoryDatabase) { 24 | final sqlite3 = await WasmSqlite3.loadFromUrl(Uri.parse('sqlite3.wasm')); 25 | sqlite3.registerVirtualFileSystem(InMemoryFileSystem(), makeDefault: true); 26 | WasmDatabase.inMemory(sqlite3); 27 | } 28 | */ 29 | if (dropDatabase) web.window.indexedDB.deleteDatabase(Config.databaseName); 30 | final result = await wasm.WasmDatabase.open( 31 | databaseName: memoryDatabase ? ':memory:' : path ?? Config.databaseName, 32 | sqlite3Uri: Uri.parse('sqlite3.wasm'), 33 | driftWorkerUri: Uri.parse('drift_worker.js'), 34 | ); 35 | 36 | /* 37 | if (dropDatabase) html.window.localStorage.clear(); 38 | return Future.value( 39 | web.WebDatabase( 40 | memoryDatabase ? ':memory:' : path ?? Config.databaseName, 41 | logStatements: logStatements, 42 | /* setup: (db) {}, */ 43 | ), 44 | ); 45 | */ 46 | 47 | if (result.missingFeatures.isNotEmpty) { 48 | // Depending how central local persistence is to your app, you may want 49 | // to show a warning to the user if only unrealiable implemetentations 50 | // are available. 51 | l.w( 52 | 'Using ${result.chosenImplementation} due to missing browser ' 53 | 'features: ${result.missingFeatures}', 54 | ); 55 | } 56 | 57 | return result.resolvedExecutor; 58 | } 59 | -------------------------------------------------------------------------------- /lib/src/common/database/platform/database_vm.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io' as io; 2 | 3 | import 'package:drift/drift.dart'; 4 | import 'package:drift/native.dart' as ffi; 5 | import 'package:flutter/foundation.dart'; 6 | import 'package:flutter_template_name/src/common/constant/config.dart'; 7 | import 'package:flutter_template_name/src/common/constant/pubspec.yaml.g.dart'; 8 | import 'package:l/l.dart'; 9 | import 'package:meta/meta.dart'; 10 | import 'package:path/path.dart' as p; 11 | import 'package:path_provider/path_provider.dart' as pp; 12 | import 'package:platform_info/platform_info.dart'; 13 | 14 | @internal 15 | Future $createQueryExecutor({ 16 | String? path, 17 | bool logStatements = false, 18 | bool dropDatabase = false, 19 | bool memoryDatabase = false, 20 | }) async { 21 | // Put this somewhere before you open your first VmDatabase 22 | 23 | if (kDebugMode) { 24 | // Close existing instances for hot restart 25 | try { 26 | ffi.NativeDatabase.closeExistingInstances(); 27 | } on Object catch (e, st) { 28 | l.w("Can't close existing database instances, error: $e", st); 29 | } 30 | } 31 | 32 | if (memoryDatabase) { 33 | return ffi.NativeDatabase.memory( 34 | logStatements: logStatements, 35 | /* setup: (db) {}, */ 36 | ); 37 | } 38 | io.File file; 39 | if (path == null) { 40 | try { 41 | var dbFolder = await pp.getApplicationDocumentsDirectory(); 42 | if (platform.desktop) { 43 | dbFolder = io.Directory(p.join(dbFolder.path, Pubspec.name)); 44 | if (!dbFolder.existsSync()) await dbFolder.create(recursive: true); 45 | } 46 | file = io.File(p.join(dbFolder.path, '${Config.databaseName}.db')); 47 | } on Object catch (error, stackTrace) { 48 | Error.throwWithStackTrace('Failed to get application documents directory "$error"', stackTrace); 49 | } 50 | } else { 51 | file = io.File(path); 52 | } 53 | try { 54 | if (dropDatabase && file.existsSync()) { 55 | await file.delete(); 56 | } 57 | } on Object catch (e, st) { 58 | l.e("Can't delete database file: $file, error: $e", st); 59 | rethrow; 60 | } 61 | /* return ffi.NativeDatabase( 62 | file, 63 | logStatements: logStatements, 64 | /* setup: (db) {}, */ 65 | ); */ 66 | return ffi.NativeDatabase.createInBackground( 67 | file, 68 | logStatements: logStatements, 69 | /* setup: (db) {}, */ 70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /lib/src/common/database/queries.dart: -------------------------------------------------------------------------------- 1 | import 'package:meta/meta.dart'; 2 | 3 | @internal 4 | const Map $queries = {}; 5 | -------------------------------------------------------------------------------- /lib/src/common/localization/generated/intl/messages_all.dart: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart 2 | // This is a library that looks up messages for specific locales by 3 | // delegating to the appropriate library. 4 | 5 | // Ignore issues from commonly used lints in this file. 6 | // ignore_for_file:implementation_imports, file_names, unnecessary_new 7 | // ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering 8 | // ignore_for_file:argument_type_not_assignable, invalid_assignment 9 | // ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases 10 | // ignore_for_file:comment_references 11 | 12 | import 'dart:async'; 13 | 14 | import 'package:flutter/foundation.dart'; 15 | import 'package:intl/intl.dart'; 16 | import 'package:intl/message_lookup_by_library.dart'; 17 | import 'package:intl/src/intl_helpers.dart'; 18 | 19 | import 'messages_en.dart' as messages_en; 20 | 21 | typedef Future LibraryLoader(); 22 | Map _deferredLibraries = {'en': () => new SynchronousFuture(null)}; 23 | 24 | MessageLookupByLibrary? _findExact(String localeName) { 25 | switch (localeName) { 26 | case 'en': 27 | return messages_en.messages; 28 | default: 29 | return null; 30 | } 31 | } 32 | 33 | /// User programs should call this before using [localeName] for messages. 34 | Future initializeMessages(String localeName) { 35 | var availableLocale = Intl.verifiedLocale( 36 | localeName, 37 | (locale) => _deferredLibraries[locale] != null, 38 | onFailure: (_) => null, 39 | ); 40 | if (availableLocale == null) { 41 | return new SynchronousFuture(false); 42 | } 43 | var lib = _deferredLibraries[availableLocale]; 44 | lib == null ? new SynchronousFuture(false) : lib(); 45 | initializeInternalMessageLookup(() => new CompositeMessageLookup()); 46 | messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor); 47 | return new SynchronousFuture(true); 48 | } 49 | 50 | bool _messagesExistFor(String locale) { 51 | try { 52 | return _findExact(locale) != null; 53 | } catch (e) { 54 | return false; 55 | } 56 | } 57 | 58 | MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) { 59 | var actualLocale = Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null); 60 | if (actualLocale == null) return null; 61 | return _findExact(actualLocale); 62 | } 63 | -------------------------------------------------------------------------------- /lib/src/common/model/app_metadata.dart: -------------------------------------------------------------------------------- 1 | import 'package:meta/meta.dart'; 2 | 3 | /// {@template app_metadata} 4 | /// App metadata 5 | /// {@endtemplate} 6 | @immutable 7 | class AppMetadata { 8 | /// {@macro app_metadata} 9 | const AppMetadata({ 10 | required this.isWeb, 11 | required this.isRelease, 12 | required this.appVersion, 13 | required this.appVersionMajor, 14 | required this.appVersionMinor, 15 | required this.appVersionPatch, 16 | required this.appBuildTimestamp, 17 | required this.appName, 18 | required this.operatingSystem, 19 | required this.processorsCount, 20 | required this.locale, 21 | required this.deviceVersion, 22 | required this.deviceScreenSize, 23 | required this.appLaunchedTimestamp, 24 | }); 25 | 26 | /// Is web platform 27 | final bool isWeb; 28 | 29 | /// Is release build 30 | final bool isRelease; 31 | 32 | /// App version 33 | final String appVersion; 34 | 35 | /// App version major 36 | final int appVersionMajor; 37 | 38 | /// App version minor 39 | final int appVersionMinor; 40 | 41 | /// App version patch 42 | final int appVersionPatch; 43 | 44 | /// App build timestamp 45 | final int appBuildTimestamp; 46 | 47 | /// App name 48 | final String appName; 49 | 50 | /// Operating system 51 | final String operatingSystem; 52 | 53 | /// Processors count 54 | final int processorsCount; 55 | 56 | /// Locale 57 | final String locale; 58 | 59 | /// Device representation 60 | final String deviceVersion; 61 | 62 | /// Device logical screen size 63 | final String deviceScreenSize; 64 | 65 | /// App launched timestamp 66 | final DateTime appLaunchedTimestamp; 67 | 68 | /// Convert to headers 69 | Map toHeaders() => { 70 | 'X-Meta-Is-Web': isWeb ? 'true' : 'false', 71 | 'X-Meta-Is-Release': isRelease ? 'true' : 'false', 72 | 'X-Meta-App-Version': appVersion, 73 | 'X-Meta-App-Version-Major': appVersionMajor.toString(), 74 | 'X-Meta-App-Version-Minor': appVersionMinor.toString(), 75 | 'X-Meta-App-Version-Patch': appVersionPatch.toString(), 76 | 'X-Meta-App-Build-Timestamp': appBuildTimestamp.toString(), 77 | 'X-Meta-App-Name': appName, 78 | 'X-Meta-Operating-System': operatingSystem, 79 | 'X-Meta-Processors-Count': processorsCount.toString(), 80 | 'X-Meta-Locale': locale, 81 | 'X-Meta-Device-Version': deviceVersion, 82 | 'X-Meta-Device-Screen-Size': deviceScreenSize, 83 | 'X-Meta-App-Launched-Timestamp': appLaunchedTimestamp.millisecondsSinceEpoch.toString(), 84 | }; 85 | } 86 | -------------------------------------------------------------------------------- /lib/src/common/model/dependencies.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_template_name/src/common/database/database.dart'; 3 | import 'package:flutter_template_name/src/common/model/app_metadata.dart'; 4 | import 'package:flutter_template_name/src/common/util/api_client.dart'; 5 | import 'package:flutter_template_name/src/feature/authentication/controller/authentication_controller.dart'; 6 | import 'package:shared_preferences/shared_preferences.dart'; 7 | 8 | /// {@template dependencies} 9 | /// Application dependencies. 10 | /// {@endtemplate} 11 | class Dependencies { 12 | /// {@macro dependencies} 13 | Dependencies(); 14 | 15 | /// The state from the closest instance of this class. 16 | /// 17 | /// {@macro dependencies} 18 | factory Dependencies.of(BuildContext context) => InheritedDependencies.of(context); 19 | 20 | /// Injest dependencies to the widget tree. 21 | Widget inject({required Widget child, Key? key}) => InheritedDependencies(dependencies: this, key: key, child: child); 22 | 23 | /// App metadata 24 | late final AppMetadata metadata; 25 | 26 | /// Shared preferences 27 | late final SharedPreferences sharedPreferences; 28 | 29 | /// Database 30 | late final Database database; 31 | 32 | /// API Client 33 | late final ApiClient apiClient; 34 | 35 | /// Authentication controller 36 | late final AuthenticationController authenticationController; 37 | 38 | @override 39 | String toString() => 'Dependencies{}'; 40 | } 41 | 42 | /// Fake Dependencies 43 | @visibleForTesting 44 | class FakeDependencies extends Dependencies { 45 | FakeDependencies(); 46 | 47 | @override 48 | dynamic noSuchMethod(Invocation invocation) { 49 | // ... implement fake dependencies 50 | throw UnimplementedError(); 51 | } 52 | } 53 | 54 | /// {@template inherited_dependencies} 55 | /// InheritedDependencies widget. 56 | /// {@endtemplate} 57 | class InheritedDependencies extends InheritedWidget { 58 | /// {@macro inherited_dependencies} 59 | const InheritedDependencies({required this.dependencies, required super.child, super.key}); 60 | 61 | final Dependencies dependencies; 62 | 63 | /// The state from the closest instance of this class 64 | /// that encloses the given context, if any. 65 | static Dependencies? maybeOf(BuildContext context) => 66 | (context.getElementForInheritedWidgetOfExactType()?.widget as InheritedDependencies?) 67 | ?.dependencies; 68 | 69 | static Never _notFoundInheritedWidgetOfExactType() => 70 | throw ArgumentError( 71 | 'Out of scope, not found inherited widget ' 72 | 'a InheritedDependencies of the exact type', 73 | 'out_of_scope', 74 | ); 75 | 76 | /// The state from the closest instance of this class 77 | /// that encloses the given context. 78 | static Dependencies of(BuildContext context) => maybeOf(context) ?? _notFoundInheritedWidgetOfExactType(); 79 | 80 | @override 81 | bool updateShouldNotify(covariant InheritedDependencies oldWidget) => false; 82 | } 83 | -------------------------------------------------------------------------------- /lib/src/common/router/home_guard.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter_template_name/src/common/router/routes.dart'; 4 | import 'package:flutter_template_name/src/feature/authentication/model/user.dart'; 5 | import 'package:octopus/octopus.dart'; 6 | 7 | /// Check routes always contain the home route at the first position. 8 | /// Only exception for not authenticated users. 9 | class HomeGuard extends OctopusGuard { 10 | HomeGuard(); 11 | 12 | static final String _homeName = Routes.home.name; 13 | 14 | @override 15 | Future call( 16 | List history, 17 | OctopusState$Mutable state, 18 | Map context, 19 | ) async { 20 | // If the user is not authenticated, do nothing. 21 | // The home route should not be in the state. 22 | if (context['user'] case User user) if (!user.isAuthenticated) return state; 23 | 24 | // Home route should be the first route in the state 25 | // and should be only one in whole state. 26 | if (state.isEmpty) return _fix(state); 27 | final count = state.findAllByName(_homeName).length; 28 | if (count != 1) return _fix(state); 29 | if (state.children.first.name != _homeName) return _fix(state); 30 | return state; 31 | } 32 | 33 | /// Change the state of the nested navigation. 34 | OctopusState _fix(OctopusState$Mutable state) => 35 | state 36 | ..clear() 37 | ..putIfAbsent(_homeName, () => Routes.home.node()); 38 | } 39 | -------------------------------------------------------------------------------- /lib/src/common/router/router_state_mixin.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart' show State, StatefulWidget, ValueNotifier; 2 | import 'package:flutter_template_name/src/common/model/dependencies.dart'; 3 | import 'package:flutter_template_name/src/common/router/authentication_guard.dart'; 4 | import 'package:flutter_template_name/src/common/router/home_guard.dart'; 5 | import 'package:flutter_template_name/src/common/router/routes.dart'; 6 | import 'package:octopus/octopus.dart'; 7 | 8 | mixin RouterStateMixin on State { 9 | late final Octopus router; 10 | late final ValueNotifier> errorsObserver; 11 | 12 | @override 13 | void initState() { 14 | final dependencies = Dependencies.of(context); 15 | // Observe all errors. 16 | errorsObserver = ValueNotifier>( 17 | <({Object error, StackTrace stackTrace})>[], 18 | ); 19 | 20 | // Create router. 21 | router = Octopus( 22 | routes: Routes.values, 23 | defaultRoute: Routes.home, 24 | guards: [ 25 | // Check authentication. 26 | AuthenticationGuard( 27 | // Get current user from authentication controller. 28 | getUser: () => dependencies.authenticationController.state.user, 29 | // Available routes for non authenticated user. 30 | routes: {Routes.signin.name, Routes.signup.name}, 31 | // Default route for non authenticated user. 32 | signinNavigation: OctopusState.single(Routes.signin.node()), 33 | // Default route for authenticated user. 34 | homeNavigation: OctopusState.single(Routes.home.node()), 35 | // Check authentication on every authentication controller state change. 36 | refresh: dependencies.authenticationController, 37 | ), 38 | // Home route should be always on top. 39 | HomeGuard(), 40 | ], 41 | onError: 42 | (error, stackTrace) => 43 | errorsObserver.value = <({Object error, StackTrace stackTrace})>[ 44 | (error: error, stackTrace: stackTrace), 45 | ...errorsObserver.value, 46 | ], 47 | /* observers: [ 48 | HeroController(), 49 | ], */ 50 | ); 51 | super.initState(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/src/common/router/routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_template_name/src/feature/account/widget/profile_screen.dart'; 3 | import 'package:flutter_template_name/src/feature/authentication/widget/signin_screen.dart'; 4 | import 'package:flutter_template_name/src/feature/authentication/widget/signup_screen.dart'; 5 | import 'package:flutter_template_name/src/feature/developer/widget/developer_screen.dart'; 6 | import 'package:flutter_template_name/src/feature/home/widget/home_screen.dart'; 7 | import 'package:flutter_template_name/src/feature/settings/widget/settings_screen.dart'; 8 | import 'package:octopus/octopus.dart'; 9 | 10 | enum Routes with OctopusRoute { 11 | signin('signin', title: 'Sign-In'), 12 | signup('signup', title: 'Sign-Up'), 13 | home('home', title: 'Octopus'), 14 | profile('profile', title: 'Profile'), 15 | developer('developer', title: 'Developer'), 16 | //settingsDialog('settings-dialog', title: 'Settings'), 17 | settings('settings', title: 'Settings'); 18 | 19 | const Routes(this.name, {this.title}); 20 | 21 | @override 22 | final String name; 23 | 24 | @override 25 | final String? title; 26 | 27 | @override 28 | Widget builder(BuildContext context, OctopusState state, OctopusNode node) => switch (this) { 29 | Routes.signin => const SignInScreen(), 30 | Routes.signup => const SignUpScreen(), 31 | Routes.home => const HomeScreen(), 32 | Routes.profile => const ProfileScreen(), 33 | Routes.developer => const DeveloperScreen(), 34 | Routes.settings => const SettingsScreen(), 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/common/util/app_zone.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:l/l.dart'; 4 | import 'package:platform_info/platform_info.dart'; 5 | 6 | /// Catch all application errors and logs. 7 | void appZone(Future Function() fn) => l.capture( 8 | () => runZonedGuarded(() => fn(), l.e), 9 | LogOptions( 10 | messageFormatting: _messageFormatting, 11 | handlePrint: true, 12 | outputInRelease: false, 13 | printColors: !platform.iOS, //? Remove when iOS will supports ANSI colors in console. 14 | ), 15 | ); 16 | 17 | /// Formats the log message. 18 | Object _messageFormatting(LogMessage log) => '${_timeFormat(log.timestamp)} | ${log.message}'; 19 | 20 | /// Formats the time. 21 | String _timeFormat(DateTime time) => '${time.hour}:${time.minute.toString().padLeft(2, '0')}'; 22 | -------------------------------------------------------------------------------- /lib/src/common/util/color_util.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_classes_with_only_static_members 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | /// Color utilities. 6 | abstract final class ColorUtil { 7 | /// Get list of colors with length [count]. 8 | static List getColors(int count) { 9 | final primariesLength = Colors.primaries.length; 10 | if (count <= primariesLength) return Colors.primaries.take(count).toList(); 11 | 12 | final colors = List.filled(count, Colors.transparent); 13 | final step = count / (primariesLength - 1); 14 | 15 | var index = 0; 16 | for (var i = 0; i < primariesLength - 1; i++) { 17 | for (var j = 0; j < step; j++) { 18 | final color1 = Colors.primaries[i], color2 = Colors.primaries[i + 1]; 19 | colors[index] = Color.lerp(color1, color2, j / step)!; 20 | index++; 21 | if (index == count) return colors; 22 | } 23 | } 24 | 25 | while (index < count) { 26 | colors[index] = Colors.primaries.last; 27 | index++; 28 | } 29 | 30 | return colors; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/common/util/error_util.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_classes_with_only_static_members 2 | 3 | import 'dart:async'; 4 | 5 | import 'package:flutter_template_name/src/common/util/platform/error_util_vm.dart' 6 | // ignore: uri_does_not_exist 7 | if (dart.library.html) 'package:flutter_template_name/src/common/util/platform/error_util_js.dart'; 8 | import 'package:l/l.dart'; 9 | 10 | /// Error util. 11 | abstract final class ErrorUtil { 12 | /// Log the error to the console and to Crashlytics. 13 | static Future logError(Object exception, StackTrace stackTrace, {String? hint, bool fatal = false}) async { 14 | try { 15 | if (exception is String) { 16 | return await logMessage(exception, stackTrace: stackTrace, hint: hint, warning: true); 17 | } 18 | $captureException(exception, stackTrace, hint, fatal).ignore(); 19 | l.e(exception, stackTrace); 20 | } on Object catch (error, stackTrace) { 21 | l.e('Error while logging error "$error" inside ErrorUtil.logError', stackTrace); 22 | } 23 | } 24 | 25 | /// Logs a message to the console and to Crashlytics. 26 | static Future logMessage(String message, {StackTrace? stackTrace, String? hint, bool warning = false}) async { 27 | try { 28 | l.e(message, stackTrace ?? StackTrace.current); 29 | $captureMessage(message, stackTrace, hint, warning).ignore(); 30 | } on Object catch (error, stackTrace) { 31 | l.e('Error while logging error "$error" inside ErrorUtil.logMessage', stackTrace); 32 | } 33 | } 34 | 35 | /// Rethrows the error with the stack trace. 36 | static Never throwWithStackTrace(Object error, StackTrace stackTrace) => Error.throwWithStackTrace(error, stackTrace); 37 | } 38 | -------------------------------------------------------------------------------- /lib/src/common/util/json_util.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_classes_with_only_static_members 2 | 3 | /// Utility class for working with JSON. 4 | sealed class JsonUtil { 5 | /// Extracts a value from [json] and casts it to [T]. 6 | static T extract(Map json, String key, [T? fallback]) { 7 | if (json[key] case T value) return value; 8 | if (fallback is T) return fallback; 9 | throw ArgumentError.value(json[key], 'value', 'is not of type $T'); 10 | } 11 | 12 | /// Extracts a value from [json] and casts it to [T]. 13 | /// Returns `null` if the value is not of type [T]. 14 | static T? extractOrNull(Map json, String key) { 15 | if (json[key] case T value) return value; 16 | return null; 17 | } 18 | 19 | /// Extracts a value from [json] and casts it to [DateTime]. 20 | /// [String] - [DateTime] format: `yyyy-MM-ddTHH:mm:ss.SSSZ` 21 | /// [int] - [DateTime] format: seconds since epoch 22 | static DateTime? extractDateTimeOrNull(Map json, String key) => switch (json[key]) { 23 | String value => DateTime.tryParse(value), 24 | int value => DateTime.fromMillisecondsSinceEpoch(value * 1000), 25 | _ => null, 26 | }; 27 | } 28 | 29 | extension JsonUtilX on Map { 30 | /// Extracts a value from [this] and casts it to [T]. 31 | T extract(String key, [T? fallback]) => JsonUtil.extract(this, key, fallback); 32 | 33 | /// Extracts a value from [this] and casts it to [T]. 34 | /// Returns `null` if the value is not of type [T]. 35 | T? extractOrNull(String key) => JsonUtil.extractOrNull(this, key); 36 | 37 | /// Extracts a value from [this] and casts it to [DateTime]. 38 | /// [String] - [DateTime] format: `yyyy-MM-ddTHH:mm:ss.SSSZ` 39 | /// [int] - [DateTime] format: seconds since epoch 40 | DateTime? extractDateTimeOrNull(String key) => JsonUtil.extractDateTimeOrNull(this, key); 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/common/util/keyboard_observer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_template_name/src/common/util/platform/keyboard_observer_interface.dart'; 2 | import 'package:flutter_template_name/src/common/util/platform/keyboard_observer_vm.dart' 3 | // ignore: uri_does_not_exist 4 | if (dart.library.html) 'package:flutter_template_name/src/common/util/platform/keyboard_observer_js.dart'; 5 | 6 | sealed class KeyboardObserver { 7 | KeyboardObserver._(); 8 | static IKeyboardObserver? _keyboardObserver; 9 | static IKeyboardObserver get instance => _keyboardObserver ??= $getKeyboardObserver(); 10 | static IKeyboardObserver get I => instance; 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/common/util/log_buffer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection' show Queue; 2 | 3 | import 'package:flutter/foundation.dart' show ChangeNotifier; 4 | import 'package:l/l.dart'; 5 | 6 | /// LogBuffer Singleton class 7 | class LogBuffer with ChangeNotifier { 8 | LogBuffer._internal(); 9 | static final LogBuffer _internalSingleton = LogBuffer._internal(); 10 | static LogBuffer get instance => _internalSingleton; 11 | 12 | static const int bufferLimit = 10000; 13 | final Queue _queue = Queue(); 14 | 15 | /// Get the logs 16 | Iterable get logs => _queue; 17 | 18 | /// Clear the logs 19 | void clear() { 20 | _queue.clear(); 21 | notifyListeners(); 22 | } 23 | 24 | /// Add a log to the buffer 25 | void add(LogMessage log) { 26 | if (_queue.length >= bufferLimit) _queue.removeFirst(); 27 | _queue.add(log); 28 | notifyListeners(); 29 | } 30 | 31 | /// Add a list of logs to the buffer 32 | void addAll(List logs) { 33 | final list = logs.take(bufferLimit).toList(); 34 | if (_queue.length + logs.length >= bufferLimit) { 35 | final toRemove = _queue.length + list.length - bufferLimit; 36 | for (var i = 0; i < toRemove; i++) _queue.removeFirst(); 37 | } 38 | _queue.addAll(list); 39 | notifyListeners(); 40 | } 41 | 42 | @override 43 | void dispose() { 44 | _queue.clear(); 45 | super.dispose(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/src/common/util/middleware/logger_mw.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer' as developer; 2 | 3 | import 'package:flutter_template_name/src/common/util/api_client.dart'; 4 | import 'package:meta/meta.dart'; 5 | 6 | @immutable 7 | class ApiClient$LoggerMiddleware { 8 | const ApiClient$LoggerMiddleware({this.logRequest = false, this.logResponse = true, this.logError = true}); 9 | 10 | final bool logRequest; 11 | final bool logResponse; 12 | final bool logError; 13 | 14 | ApiClientHandler call(ApiClientHandler innerHandler) => (request, context) async { 15 | final stopwatch = Stopwatch()..start(); 16 | try { 17 | if (logRequest) { 18 | developer.log('[${request.method}] ${request.url.path}', name: 'http', time: DateTime.now(), level: 300); 19 | } 20 | final response = await innerHandler(request, context); 21 | if (logResponse) { 22 | developer.log( 23 | '[${request.method}] ${request.url.path} -> ok | ${stopwatch.elapsedMilliseconds}ms', 24 | name: 'http', 25 | time: DateTime.now(), 26 | level: 300, 27 | ); 28 | } 29 | return response; 30 | } on APIClientException catch (error, stackTrace) { 31 | if (logError) { 32 | developer.log( 33 | '[${request.method}] ${request.url.path} -> ${switch (error.statusCode) { 34 | 501 => 'unimplemented', 35 | 500 => 'internalError', 36 | 409 => 'aborted', 37 | 404 => 'notFound', 38 | 403 => 'permissionDenied', 39 | 401 => 'unauthenticated', 40 | 400 => 'failedPrecondition', 41 | < 400 => 'unknownError', 42 | int statusCode => '$statusCode', 43 | }} | ${stopwatch.elapsedMilliseconds}ms', 44 | name: 'http', 45 | time: DateTime.now(), 46 | level: 900, 47 | error: error.statusCode >= 400 ? null : error, 48 | stackTrace: error.statusCode >= 400 ? null : stackTrace, 49 | ); 50 | } 51 | rethrow; 52 | } finally { 53 | stopwatch.stop(); 54 | } 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /lib/src/common/util/placeholders.dart: -------------------------------------------------------------------------------- 1 | // Generator of random words, sentences and paragraphs. 2 | 3 | import 'dart:convert'; 4 | import 'dart:math' as math; 5 | 6 | final _$rnd = math.Random(); 7 | const _$nbsp = '\u00A0'; 8 | 9 | /// Generate a random word with a length between [min] and [max] (inclusive). 10 | String generateWord([int min = 1, int max = 8]) => utf8.decode( 11 | List.generate(_$rnd.nextInt(max - min + 1) + min, (_) => _$rnd.nextInt(26) + 97), 12 | allowMalformed: false, 13 | ); 14 | 15 | /// Generate a random sentence with a length between [min] and [max] (inclusive). 16 | String generateSentence([int min = 2, int max = 8]) => 17 | (StringBuffer() 18 | ..write(() { 19 | final word = generateWord(); 20 | return '${word[0].toUpperCase()}${word.substring(1)}'; 21 | }()) 22 | ..writeAll(List.generate(_$rnd.nextInt(max - min + 1) + min, (_) => generateWord()), _$nbsp) 23 | ..write('.')) 24 | .toString(); 25 | 26 | /// Generate a random paragraph with a length between [min] and [max] (inclusive). 27 | String generateParagraph([int min = 15, int max = 30]) => 28 | Iterable.generate(_$rnd.nextInt(15) + 15, (_) => generateSentence()).join(' '); 29 | -------------------------------------------------------------------------------- /lib/src/common/util/platform/error_util_js.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_positional_boolean_parameters 2 | 3 | Future $captureException(Object exception, StackTrace stackTrace, String? hint, bool fatal) => 4 | Future.value(null); 5 | 6 | Future $captureMessage(String message, StackTrace? stackTrace, String? hint, bool warning) => 7 | Future.value(null); 8 | -------------------------------------------------------------------------------- /lib/src/common/util/platform/error_util_vm.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_positional_boolean_parameters 2 | //import 'package:firebase_crashlytics/firebase_crashlytics.dart'; 3 | 4 | /* 5 | * Sentry.captureException(exception, stackTrace: stackTrace, hint: hint); 6 | * FirebaseCrashlytics.instance 7 | * .recordError(exception, stackTrace ?? StackTrace.current, reason: hint, fatal: fatal); 8 | * */ 9 | Future $captureException(Object exception, StackTrace stackTrace, String? hint, bool fatal) => 10 | Future.value(); 11 | // FirebaseCrashlytics.instance.recordError(exception, stackTrace, reason: hint, fatal: fatal); 12 | 13 | /* 14 | * Sentry.captureMessage( 15 | * message, 16 | * level: warning ? SentryLevel.warning : SentryLevel.info, 17 | * hint: hint, 18 | * params: [ 19 | * ...?params, 20 | * if (stackTrace != null) 'StackTrace: $stackTrace', 21 | * ], 22 | * ); 23 | * (warning || stackTrace != null) 24 | * ? FirebaseCrashlytics.instance.recordError(message, stackTrace ?? StackTrace.current); 25 | * : FirebaseCrashlytics.instance.log('$message${hint != null ? '\r\n$hint' : ''}'); 26 | * */ 27 | Future $captureMessage(String message, StackTrace? stackTrace, String? hint, bool warning) => 28 | Future.value(); 29 | /* warning || stackTrace != null 30 | ? FirebaseCrashlytics.instance.recordError( 31 | message, 32 | stackTrace ?? StackTrace.current, 33 | reason: hint, 34 | fatal: false, 35 | ) 36 | : FirebaseCrashlytics.instance.log('$message' 37 | '${stackTrace != null ? '\nHint: $hint' : ''}'); */ 38 | -------------------------------------------------------------------------------- /lib/src/common/util/platform/keyboard_observer_interface.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | /// An interface for observing keyboard events. 4 | abstract class IKeyboardObserver implements Listenable { 5 | /// Returns true if a CTRL modifier key is pressed, regardless of which side 6 | /// of the keyboard it is on. 7 | /// 8 | /// Use [isKeyPressed] if you need to know which control key was pressed. 9 | bool get isControlPressed; 10 | 11 | /// Returns true if a SHIFT modifier key is pressed, regardless of which side 12 | /// of the keyboard it is on. 13 | /// 14 | /// Use [isKeyPressed] if you need to know which shift key was pressed. 15 | bool get isShiftPressed; 16 | 17 | /// Returns true if a ALT modifier key is pressed, regardless of which side 18 | /// of the keyboard it is on. 19 | /// 20 | /// Use [isKeyPressed] if you need to know which alt key was pressed. 21 | bool get isAltPressed; 22 | 23 | /// Returns true if a META modifier key is pressed, regardless of which side 24 | /// of the keyboard it is on. 25 | /// 26 | /// Use [isKeyPressed] if you need to know which meta key was pressed. 27 | bool get isMetaPressed; 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/common/util/platform/keyboard_observer_js.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:flutter_template_name/src/common/util/platform/keyboard_observer_interface.dart'; 4 | import 'package:meta/meta.dart'; 5 | 6 | IKeyboardObserver $getKeyboardObserver() => _KeyboardObserver$JS(); 7 | 8 | @sealed 9 | class _KeyboardObserver$JS with _IsKeyPressed$JS, ChangeNotifier implements IKeyboardObserver { 10 | @override 11 | bool get isControlPressed => 12 | isKeyPressed(LogicalKeyboardKey.controlLeft) || isKeyPressed(LogicalKeyboardKey.controlRight); 13 | 14 | @override 15 | bool get isShiftPressed => isKeyPressed(LogicalKeyboardKey.shiftLeft) || isKeyPressed(LogicalKeyboardKey.shiftRight); 16 | 17 | @override 18 | bool get isAltPressed => isKeyPressed(LogicalKeyboardKey.altLeft) || isKeyPressed(LogicalKeyboardKey.altRight); 19 | 20 | @override 21 | bool get isMetaPressed => isKeyPressed(LogicalKeyboardKey.metaLeft) || isKeyPressed(LogicalKeyboardKey.metaRight); 22 | } 23 | 24 | mixin _IsKeyPressed$JS { 25 | /// Returns true if the given [KeyboardKey] is pressed. 26 | bool isKeyPressed(LogicalKeyboardKey key) => HardwareKeyboard.instance.logicalKeysPressed.contains(key); 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/common/util/platform/keyboard_observer_vm.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io' as io; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:flutter_template_name/src/common/util/platform/keyboard_observer_interface.dart'; 6 | import 'package:flutter_template_name/src/common/util/platform/keyboard_observer_windows.dart'; 7 | import 'package:meta/meta.dart'; 8 | 9 | IKeyboardObserver $getKeyboardObserver() => 10 | io.Platform.isWindows ? $getKeyboardObserver$Windows() : _KeyboardObserver$VM(); 11 | 12 | @sealed 13 | class _KeyboardObserver$VM with _IsKeyPressed$IO, ChangeNotifier implements IKeyboardObserver { 14 | @override 15 | bool get isControlPressed => 16 | isKeyPressed(LogicalKeyboardKey.controlLeft) || isKeyPressed(LogicalKeyboardKey.controlRight); 17 | 18 | @override 19 | bool get isShiftPressed => isKeyPressed(LogicalKeyboardKey.shiftLeft) || isKeyPressed(LogicalKeyboardKey.shiftRight); 20 | 21 | @override 22 | bool get isAltPressed => isKeyPressed(LogicalKeyboardKey.altLeft) || isKeyPressed(LogicalKeyboardKey.altRight); 23 | 24 | @override 25 | bool get isMetaPressed => isKeyPressed(LogicalKeyboardKey.metaLeft) || isKeyPressed(LogicalKeyboardKey.metaRight); 26 | } 27 | 28 | mixin _IsKeyPressed$IO { 29 | /// Returns true if the given [KeyboardKey] is pressed. 30 | bool isKeyPressed(LogicalKeyboardKey key) => HardwareKeyboard.instance.logicalKeysPressed.contains(key); 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/common/util/platform/keyboard_observer_windows.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter_template_name/src/common/model/virtual_key_codes.dart'; 3 | import 'package:flutter_template_name/src/common/util/platform/keyboard_observer_interface.dart'; 4 | import 'package:meta/meta.dart'; 5 | import 'package:win32/win32.dart' show GetKeyState; // GetAsyncKeyState, GetKeyboardState 6 | 7 | IKeyboardObserver $getKeyboardObserver$Windows() => _KeyboardObserver$Windows(); 8 | 9 | @sealed 10 | class _KeyboardObserver$Windows with _IsKeyPressed, ChangeNotifier implements IKeyboardObserver { 11 | @override 12 | bool get isControlPressed => isKeyPressed(VK.LCONTROL) || isKeyPressed(VK.RCONTROL); 13 | 14 | @override 15 | bool get isShiftPressed => isKeyPressed(VK.LSHIFT) || isKeyPressed(VK.RSHIFT); 16 | 17 | @override 18 | bool get isAltPressed => isKeyPressed(VK.LMENU) || isKeyPressed(VK.RMENU); 19 | 20 | @override 21 | bool get isMetaPressed => isKeyPressed(VK.LWIN) || isKeyPressed(VK.RWIN); 22 | } 23 | 24 | mixin _IsKeyPressed { 25 | /// Returns true if the given [VK] is pressed. 26 | bool isKeyPressed(VK key) { 27 | final state = GetKeyState(key.code); 28 | return state != 0 && state != 1; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/common/util/timeouts.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | /// Extension methods for [Future]. 4 | extension TimeoutsExtension on Future { 5 | /// Returns a [Future] that completes with this future's result, or with the 6 | /// result of calling the [onTimeout] function, if this future doesn't 7 | /// complete before the timeout is exceeded. 8 | /// 9 | /// The [onTimeout] function must return a [Future] which will be used as the 10 | /// result of the returned [Future], and must not throw. 11 | Future logicTimeout({double coefficient = 1, Future Function()? onTimeout}) => 12 | timeout(const Duration(milliseconds: 20000) * coefficient, onTimeout: onTimeout); 13 | } 14 | -------------------------------------------------------------------------------- /lib/src/common/util/token_util.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_classes_with_only_static_members 2 | 3 | import 'dart:convert'; 4 | import 'dart:math' as math; 5 | 6 | sealed class TokenUtil { 7 | static final math.Random _random = math.Random.secure(); 8 | 9 | /// Generate a random token. 10 | static String generate([int length = 64]) { 11 | var byteLength = (length * 6 + 7) ~/ 8; 12 | var values = List.generate(byteLength, (i) => _random.nextInt(256)); 13 | var token = base64Url.encode(values); 14 | assert(token.length >= length, 'Token length to short'); 15 | return token.length > length ? token.substring(0, length) : token; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/common/widget/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_localizations/flutter_localizations.dart'; 3 | import 'package:flutter_template_name/src/common/constant/config.dart'; 4 | import 'package:flutter_template_name/src/common/localization/localization.dart'; 5 | import 'package:flutter_template_name/src/common/router/router_state_mixin.dart'; 6 | import 'package:flutter_template_name/src/common/widget/window_scope.dart'; 7 | import 'package:flutter_template_name/src/feature/authentication/widget/authentication_scope.dart'; 8 | import 'package:octopus/octopus.dart'; 9 | 10 | /// {@template app} 11 | /// App widget. 12 | /// {@endtemplate} 13 | class App extends StatefulWidget { 14 | /// {@macro app} 15 | const App({super.key}); 16 | 17 | @override 18 | State createState() => _AppState(); 19 | } 20 | 21 | class _AppState extends State with RouterStateMixin { 22 | final Key builderKey = GlobalKey(); // Disable recreate widget tree 23 | 24 | @override 25 | Widget build(BuildContext context) => MaterialApp.router( 26 | title: 'Application', 27 | debugShowCheckedModeBanner: !Config.environment.isProduction, 28 | 29 | // Router 30 | routerConfig: router.config, 31 | 32 | // Localizations 33 | localizationsDelegates: const >[ 34 | GlobalMaterialLocalizations.delegate, 35 | GlobalWidgetsLocalizations.delegate, 36 | GlobalCupertinoLocalizations.delegate, 37 | Localization.delegate, 38 | ], 39 | supportedLocales: Localization.supportedLocales, 40 | /* locale: SettingsScope.localOf(context), */ 41 | 42 | // Theme 43 | /* theme: SettingsScope.themeOf(context), */ 44 | theme: ThemeData.dark(), 45 | 46 | // Scopes 47 | builder: 48 | (context, child) => MediaQuery( 49 | key: builderKey, 50 | data: MediaQuery.of(context).copyWith(textScaler: TextScaler.noScaling), 51 | child: WindowScope( 52 | title: Localization.of(context).title, 53 | child: OctopusTools( 54 | enable: true, 55 | octopus: router, 56 | child: AuthenticationScope(child: child ?? const SizedBox.shrink()), 57 | ), 58 | ), 59 | ), 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /lib/src/common/widget/app_error.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// {@template app_error} 4 | /// AppError widget 5 | /// {@endtemplate} 6 | class AppError extends StatelessWidget { 7 | /// {@macro app_error} 8 | const AppError({this.error, super.key}); 9 | 10 | /// Error 11 | final Object? error; 12 | 13 | @override 14 | Widget build(BuildContext context) => MaterialApp( 15 | title: 'App Error', 16 | theme: 17 | View.of(context).platformDispatcher.platformBrightness == Brightness.dark 18 | ? ThemeData.dark(useMaterial3: true) 19 | : ThemeData.light(useMaterial3: true), 20 | home: Scaffold( 21 | body: SafeArea( 22 | child: Center( 23 | child: Padding( 24 | padding: const EdgeInsets.all(8), 25 | child: Text( 26 | // ErrorUtil.formatMessage(error) 27 | error?.toString() ?? 'Something went wrong', 28 | textScaler: TextScaler.noScaling, 29 | ), 30 | ), 31 | ), 32 | ), 33 | ), 34 | builder: 35 | (context, child) => 36 | MediaQuery(data: MediaQuery.of(context).copyWith(textScaler: TextScaler.noScaling), child: child!), 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /lib/src/common/widget/common_actions.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/widgets.dart'; 5 | import 'package:flutter_template_name/src/common/widget/history_button.dart'; 6 | import 'package:flutter_template_name/src/feature/account/widget/profile_icon_button.dart'; 7 | import 'package:flutter_template_name/src/feature/authentication/widget/log_out_button.dart'; 8 | import 'package:flutter_template_name/src/feature/developer/widget/developer_button.dart'; 9 | 10 | class CommonActions extends ListBase { 11 | CommonActions([List? actions]) 12 | : _actions = [ 13 | ...?actions, 14 | if (kDebugMode) const DeveloperButton(), 15 | const HistoryButton(), 16 | const ProfileIconButton(), 17 | const LogOutButton(), 18 | ]; 19 | 20 | final List _actions; 21 | 22 | @override 23 | int get length => _actions.length; 24 | 25 | @override 26 | set length(int newLength) => _actions.length = newLength; 27 | 28 | @override 29 | Widget operator [](int index) => _actions[index]; 30 | 31 | @override 32 | void operator []=(int index, Widget value) => _actions[index] = value; 33 | } 34 | -------------------------------------------------------------------------------- /lib/src/common/widget/common_header.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_template_name/src/common/widget/common_actions.dart'; 3 | 4 | class SliverCommonHeader extends SliverAppBar { 5 | SliverCommonHeader({ 6 | super.leading, 7 | super.automaticallyImplyLeading = true, 8 | super.pinned = true, 9 | super.floating = true, 10 | super.snap = true, 11 | super.title, 12 | super.surfaceTintColor = Colors.transparent, 13 | List? actions, 14 | super.key, 15 | }) : super(actions: actions ?? CommonActions()); 16 | } 17 | 18 | class CommonHeader extends AppBar { 19 | CommonHeader({ 20 | super.leading, 21 | super.automaticallyImplyLeading = true, 22 | super.title, 23 | super.surfaceTintColor = Colors.transparent, 24 | List? actions, 25 | super.key, 26 | }) : super(actions: actions ?? CommonActions()); 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/common/widget/not_found_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_template_name/src/common/widget/common_actions.dart'; 3 | 4 | /// {@template not_found} 5 | /// NotFoundScreen widget. 6 | /// {@endtemplate} 7 | class NotFoundScreen extends StatelessWidget { 8 | /// {@macro not_found} 9 | const NotFoundScreen({this.title, this.message, super.key}); 10 | 11 | final String? title; 12 | final String? message; 13 | 14 | @override 15 | Widget build(BuildContext context) => Scaffold( 16 | appBar: AppBar( 17 | title: Text(title ?? 'Not found', maxLines: 1, overflow: TextOverflow.ellipsis), 18 | actions: CommonActions(), 19 | bottom: const PreferredSize( 20 | preferredSize: Size.fromHeight(48), 21 | child: SizedBox( 22 | height: 48, 23 | /* child: Breadcrumbs( 24 | breadcrumbs: { 25 | const Text('Shop'): () => AppRouter.of(context).navTab( 26 | (state) => [], 27 | tab: 'shop', 28 | activate: true, 29 | ), 30 | for (var i = 0; i < prevRoutes.length; i++) 31 | Text( 32 | ProductScope.getCategoryByID( 33 | context, prevRoutes[i].arguments['id']!) 34 | .title): () => AppRouter.of(context).navTab( 35 | (state) => state.take(i + 1).toList(growable: false), 36 | tab: 'shop', 37 | activate: true, 38 | ), 39 | const Text('Not found'): null, 40 | }, 41 | ), */ 42 | ), 43 | ), 44 | ), 45 | body: SafeArea(child: Center(child: Text(message ?? 'Content not found'))), 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /lib/src/common/widget/outlined_text.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// {@template outlined_text} 4 | /// OutlinedText widget. 5 | /// {@endtemplate} 6 | class OutlinedText extends StatelessWidget { 7 | /// {@macro outlined_text} 8 | const OutlinedText( 9 | this.text, { 10 | this.style = const TextStyle(), 11 | this.strokeWidth = 4, 12 | this.fillColor, 13 | this.strokeColor, 14 | this.maxLines = 1, 15 | super.key, // ignore: unused_element_parameter 16 | }); 17 | 18 | final String text; 19 | 20 | final int maxLines; 21 | final TextStyle style; 22 | final double strokeWidth; 23 | final Color? fillColor; 24 | final Color? strokeColor; 25 | 26 | @override 27 | Widget build(BuildContext context) => Stack( 28 | children: [ 29 | // Stroked text as border. 30 | Text( 31 | text, 32 | maxLines: maxLines, 33 | overflow: TextOverflow.ellipsis, 34 | textAlign: TextAlign.center, 35 | style: style.copyWith( 36 | height: 0, 37 | foreground: 38 | Paint() 39 | ..style = PaintingStyle.stroke 40 | ..strokeWidth = strokeWidth 41 | ..color = strokeColor ?? Colors.black45, 42 | ), 43 | ), 44 | // Solid text as fill. 45 | Text( 46 | text, 47 | maxLines: maxLines, 48 | overflow: TextOverflow.ellipsis, 49 | textAlign: TextAlign.center, 50 | style: style.copyWith(height: 0, color: fillColor ?? Colors.grey.shade300), 51 | ), 52 | ], 53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /lib/src/common/widget/output_text_field.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | /// {@template output_field} 5 | /// OutputField widget. 6 | /// {@endtemplate} 7 | class OutputTextField extends StatelessWidget { 8 | /// {@macro output_field} 9 | const OutputTextField({ 10 | required this.controller, 11 | required this.output, 12 | this.enabled = true, 13 | this.label, 14 | this.hint, 15 | this.multiline = false, 16 | this.expands = false, 17 | this.floatingLabelBehavior, 18 | this.focusNode, 19 | this.minLines, 20 | this.maxLength, 21 | this.prefixIcon, 22 | this.suffixIcon, 23 | this.order, 24 | super.key, // ignore: unused_element_parameter 25 | }); 26 | 27 | final ValueListenable controller; 28 | final String Function(T value) output; 29 | final bool enabled; 30 | final String? label; 31 | final String? hint; 32 | final bool multiline; 33 | final bool expands; 34 | final FloatingLabelBehavior? floatingLabelBehavior; 35 | final FocusNode? focusNode; 36 | final int? minLines; 37 | final int? maxLength; 38 | final Widget? prefixIcon; 39 | final Widget? suffixIcon; 40 | final double? order; 41 | 42 | Widget focusOrder({required Widget child}) { 43 | if (order case double value) return FocusTraversalOrder(order: NumericFocusOrder(value), child: child); 44 | return child; 45 | } 46 | 47 | @override 48 | Widget build(BuildContext context) => focusOrder( 49 | child: SizedBox( 50 | height: 56, 51 | child: Center( 52 | child: ValueListenableBuilder( 53 | valueListenable: controller, 54 | builder: (context, value, child) { 55 | final text = output(value); 56 | return InputDecorator( 57 | expands: multiline && expands, 58 | isEmpty: text.isEmpty, 59 | baseStyle: Theme.of(context).textTheme.bodyMedium, 60 | decoration: InputDecoration( 61 | isCollapsed: false, 62 | isDense: false, 63 | filled: true, 64 | floatingLabelBehavior: floatingLabelBehavior, 65 | contentPadding: const EdgeInsets.fromLTRB(16, 8, 4, 8), 66 | //hoverColor: colorScheme.surface, 67 | labelText: label, 68 | hintText: hint, 69 | helperText: null, 70 | prefixIcon: prefixIcon, 71 | prefixIconConstraints: const BoxConstraints.expand(width: 48, height: 48), 72 | suffixIcon: suffixIcon, 73 | suffixIconConstraints: const BoxConstraints.expand(width: 48, height: 48), 74 | counter: const SizedBox.shrink(), 75 | errorText: null, 76 | helperMaxLines: 0, 77 | errorMaxLines: 0, 78 | ), 79 | child: Text(text, maxLines: 1, overflow: TextOverflow.ellipsis), 80 | ); 81 | }, 82 | ), 83 | ), 84 | ), 85 | ); 86 | } 87 | -------------------------------------------------------------------------------- /lib/src/common/widget/scaffold_padding.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_template_name/src/common/constant/config.dart'; 5 | 6 | /// {@template scaffold_padding} 7 | /// ScaffoldPadding widget. 8 | /// {@endtemplate} 9 | class ScaffoldPadding extends EdgeInsets { 10 | const ScaffoldPadding._(final double value) : super.symmetric(horizontal: value); 11 | 12 | /// {@macro scaffold_padding} 13 | factory ScaffoldPadding.of(BuildContext context) => 14 | ScaffoldPadding._(math.max((MediaQuery.sizeOf(context).width - Config.maxScreenLayoutWidth) / 2, 16)); 15 | 16 | /// {@macro scaffold_padding} 17 | static Widget widget(BuildContext context, [Widget? child]) => 18 | Padding(padding: ScaffoldPadding.of(context), child: child); 19 | 20 | /// {@macro scaffold_padding} 21 | static Widget sliver(BuildContext context, [Widget? child]) => 22 | SliverPadding(padding: ScaffoldPadding.of(context), sliver: child); 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/common/widget/scroll_with_mouse_behavior.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/gestures.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | @immutable 5 | class ScrollWithMouseBehavior extends MaterialScrollBehavior { 6 | const ScrollWithMouseBehavior(); 7 | 8 | // Override behavior methods and getters like dragDevices 9 | @override 10 | Set get dragDevices => { 11 | PointerDeviceKind.touch, 12 | PointerDeviceKind.mouse, 13 | PointerDeviceKind.stylus, 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/common/widget/sizer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/rendering.dart'; 2 | import 'package:flutter/scheduler.dart'; 3 | import 'package:flutter/widgets.dart'; 4 | 5 | /// Measure and call callback after child size changed. 6 | class Sizer extends SingleChildRenderObjectWidget { 7 | const Sizer({required super.child, this.onSizeChanged, this.dispatchNotification = false, super.key}); 8 | 9 | /// Callback when child size changed and after layout rebuild. 10 | final void Function(Size size)? onSizeChanged; 11 | 12 | /// Send [SizeChangedLayoutNotification] notification. 13 | final bool dispatchNotification; 14 | 15 | @override 16 | RenderObject createRenderObject(BuildContext context) => _SizerRenderObject((size) { 17 | final fn = onSizeChanged; 18 | if (fn != null) { 19 | SchedulerBinding.instance.addPostFrameCallback((_) => fn(size)); 20 | } 21 | if (dispatchNotification) { 22 | SizeChangedNotification(size).dispatch(context); 23 | } 24 | }); 25 | } 26 | 27 | /// Render object for [Sizer]. 28 | class _SizerRenderObject extends RenderProxyBox { 29 | _SizerRenderObject(this.onLayoutChangedCallback); 30 | 31 | @override 32 | void debugFillProperties(DiagnosticPropertiesBuilder properties) => 33 | super.debugFillProperties(properties..add(StringProperty('oldSize', size.toString()))); 34 | 35 | /// Callback when child size changed and after layout rebuild. 36 | final void Function(Size size) onLayoutChangedCallback; 37 | 38 | /// Old size. 39 | Size? _oldSize; 40 | 41 | @override 42 | void performLayout() { 43 | super.performLayout(); 44 | final content = child; 45 | assert(content is RenderBox, 'Must contain content'); 46 | assert(content?.hasSize ?? false, 'Content must obtain a size'); 47 | final newSize = content?.size; 48 | if (newSize == null || newSize == _oldSize) return; 49 | _oldSize = newSize; 50 | onLayoutChangedCallback(newSize); 51 | } 52 | } 53 | 54 | /// Notification about size changed. 55 | @immutable 56 | class SizeChangedNotification extends SizeChangedLayoutNotification { 57 | const SizeChangedNotification(this.size); 58 | 59 | /// Current size of nested widget. 60 | final Size size; 61 | } 62 | -------------------------------------------------------------------------------- /lib/src/common/widget/try_again_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// {@template try_again_widget} 4 | /// TryAgainWidget widget. 5 | /// {@endtemplate} 6 | class TryAgainWidget extends StatelessWidget { 7 | /// {@macro try_again_widget} 8 | const TryAgainWidget(this.tryAgain, {super.key}); 9 | 10 | /// Try again callback 11 | final void Function(BuildContext context) tryAgain; 12 | 13 | @override 14 | Widget build(BuildContext context) => LayoutBuilder( 15 | builder: 16 | (context, constraints) => Center( 17 | child: IconButton.filledTonal( 18 | onPressed: () => tryAgain(context), 19 | iconSize: switch (constraints.biggest.shortestSide) { 20 | > 128 => 128, 21 | > 64 => 64, 22 | > 48 => 48, 23 | > 32 => 32, 24 | > 24 => 24, 25 | _ => constraints.biggest.shortestSide, 26 | }, 27 | padding: switch (constraints.biggest.shortestSide) { 28 | > 96 => const EdgeInsets.all(16), 29 | > 64 => const EdgeInsets.all(8), 30 | > 40 => const EdgeInsets.all(4), 31 | _ => EdgeInsets.zero, 32 | }, 33 | tooltip: MaterialLocalizations.of(context).refreshIndicatorSemanticLabel, 34 | color: Theme.of(context).colorScheme.error, 35 | icon: const Icon(Icons.refresh), 36 | ), 37 | ), 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /lib/src/feature/account/widget/profile_icon_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:flutter_template_name/src/common/localization/localization.dart'; 4 | import 'package:flutter_template_name/src/common/router/routes.dart'; 5 | import 'package:octopus/octopus.dart'; 6 | 7 | /// {@template profile_icon_button} 8 | /// ProfileIconButton widget 9 | /// {@endtemplate} 10 | class ProfileIconButton extends StatelessWidget { 11 | /// {@macro profile_icon_button} 12 | const ProfileIconButton({super.key}); 13 | 14 | @override 15 | Widget build(BuildContext context) => IconButton( 16 | icon: const Icon(Icons.person), 17 | tooltip: Localization.of(context).profileButton, 18 | onPressed: () { 19 | Octopus.maybeOf(context)?.setState( 20 | (state) => 21 | state 22 | ..removeByName(Routes.profile.name) 23 | ..add(Routes.profile.node()), 24 | ); 25 | HapticFeedback.mediumImpact().ignore(); 26 | }, 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/feature/authentication/model/sign_in_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | @immutable 4 | final class SignInData { 5 | const SignInData({required this.username, required this.password}); 6 | 7 | /// Username. 8 | final String username; 9 | 10 | /// Password. 11 | final String password; 12 | } 13 | -------------------------------------------------------------------------------- /lib/src/feature/authentication/widget/log_out_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:flutter_template_name/src/common/localization/localization.dart'; 4 | import 'package:flutter_template_name/src/feature/authentication/widget/authentication_scope.dart'; 5 | 6 | /// {@template log_out_button} 7 | /// LogOutButton widget 8 | /// {@endtemplate} 9 | class LogOutButton extends StatelessWidget { 10 | /// {@macro log_out_button} 11 | const LogOutButton({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) => IconButton( 15 | icon: const Icon(Icons.logout), 16 | tooltip: Localization.of(context).logOutButton, 17 | onPressed: 18 | () => showDialog( 19 | context: context, 20 | builder: 21 | (context) => AlertDialog( 22 | title: Center( 23 | child: Row( 24 | mainAxisSize: MainAxisSize.min, 25 | mainAxisAlignment: MainAxisAlignment.center, 26 | crossAxisAlignment: CrossAxisAlignment.center, 27 | children: [ 28 | const Icon(Icons.logout, size: 24), 29 | const SizedBox(width: 16), 30 | Text( 31 | Localization.of(context).logOutButton, 32 | maxLines: 1, 33 | overflow: TextOverflow.ellipsis, 34 | style: Theme.of(context).textTheme.headlineSmall?.copyWith(height: 1), 35 | ), 36 | const SizedBox(width: 24), 37 | ], 38 | ), 39 | ), 40 | content: Text( 41 | 'Are you sure you want to log out?', 42 | textAlign: TextAlign.center, 43 | style: Theme.of(context).textTheme.bodyMedium, 44 | ), 45 | actionsAlignment: MainAxisAlignment.spaceBetween, 46 | actions: [ 47 | SizedBox( 48 | height: 48, 49 | width: 128, 50 | child: FilledButton.icon( 51 | icon: const Icon(Icons.logout), 52 | label: Text(Localization.of(context).logOutButton), 53 | onPressed: () { 54 | AuthenticationScope.signOut(context); 55 | HapticFeedback.mediumImpact().ignore(); 56 | }, 57 | ), 58 | ), 59 | SizedBox( 60 | height: 48, 61 | width: 128, 62 | child: ElevatedButton.icon( 63 | icon: const Icon(Icons.cancel), 64 | label: Text(Localization.of(context).cancelButton), 65 | onPressed: () => Navigator.pop(context), 66 | ), 67 | ), 68 | ], 69 | ), 70 | ), 71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /lib/src/feature/authentication/widget/signup_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | 3 | import 'package:ui/ui.dart'; 4 | 5 | /// {@template signup_screen} 6 | /// SignUpScreen widget. 7 | /// {@endtemplate} 8 | class SignUpScreen extends StatelessWidget { 9 | /// {@macro signup_screen} 10 | const SignUpScreen({super.key}); 11 | 12 | @override 13 | Widget build(BuildContext context) => Scaffold( 14 | body: SafeArea( 15 | child: Center( 16 | child: LayoutBuilder( 17 | builder: 18 | (context, constraints) => SingleChildScrollView( 19 | padding: EdgeInsets.symmetric(horizontal: math.max(16, (constraints.maxWidth - 620) / 2)), 20 | child: Column( 21 | mainAxisAlignment: MainAxisAlignment.center, 22 | children: [ 23 | SizedBox( 24 | height: 50, 25 | child: Text( 26 | 'Sign-Up', 27 | maxLines: 1, 28 | overflow: TextOverflow.ellipsis, 29 | textAlign: TextAlign.center, 30 | style: Theme.of(context).textTheme.headlineLarge?.copyWith(height: 1), 31 | ), 32 | ), 33 | const SizedBox(height: 32), 34 | const FormPlaceholder(), 35 | const SizedBox(height: 32), 36 | SizedBox( 37 | height: 48, 38 | child: _SignUpScreen$Buttons(cancel: () => Navigator.pop(context), signUp: null), 39 | ), 40 | ], 41 | ), 42 | ), 43 | ), 44 | ), 45 | ), 46 | ); 47 | } 48 | 49 | class _SignUpScreen$Buttons extends StatelessWidget { 50 | const _SignUpScreen$Buttons({ 51 | required this.signUp, 52 | required this.cancel, 53 | super.key, // ignore: unused_element_parameter 54 | }); 55 | 56 | final void Function()? signUp; 57 | final void Function()? cancel; 58 | 59 | @override 60 | Widget build(BuildContext context) => Row( 61 | mainAxisSize: MainAxisSize.max, 62 | crossAxisAlignment: CrossAxisAlignment.stretch, 63 | children: [ 64 | Expanded( 65 | flex: 2, 66 | child: ElevatedButton.icon( 67 | onPressed: signUp, 68 | icon: const Icon(Icons.person_add), 69 | label: const Text('Sign-Up', maxLines: 1, overflow: TextOverflow.ellipsis), 70 | ), 71 | ), 72 | const SizedBox(width: 16), 73 | Expanded( 74 | flex: 1, 75 | child: FilledButton.tonalIcon( 76 | onPressed: cancel, 77 | icon: const Icon(Icons.cancel), 78 | label: const Text('Cancel', maxLines: 1, overflow: TextOverflow.ellipsis), 79 | ), 80 | ), 81 | ], 82 | ); 83 | } 84 | -------------------------------------------------------------------------------- /lib/src/feature/developer/widget/developer_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_template_name/src/common/localization/localization.dart'; 3 | import 'package:flutter_template_name/src/common/router/routes.dart'; 4 | import 'package:octopus/octopus.dart'; 5 | 6 | /// {@template developer_button} 7 | /// DeveloperButton widget 8 | /// {@endtemplate} 9 | class DeveloperButton extends StatelessWidget { 10 | /// {@macro developer_button} 11 | const DeveloperButton({super.key}); 12 | 13 | @override 14 | Widget build(BuildContext context) => IconButton( 15 | icon: const Icon(Icons.developer_mode), 16 | tooltip: Localization.of(context).developer, 17 | onPressed: () => Octopus.of(context).push(Routes.developer), 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/feature/home/widget/home_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_template_name/src/common/widget/common_actions.dart'; 2 | import 'package:ui/ui.dart'; 3 | 4 | /// {@template home_screen} 5 | /// HomeScreen widget. 6 | /// {@endtemplate} 7 | class HomeScreen extends StatelessWidget { 8 | /// {@macro home_screen} 9 | const HomeScreen({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) => Scaffold( 13 | body: CustomScrollView( 14 | slivers: [ 15 | SliverAppBar( 16 | pinned: true, 17 | title: const Text('Home'), 18 | leading: const SizedBox.shrink(), 19 | actions: CommonActions(), 20 | ), 21 | const SliverFillRemaining( 22 | hasScrollBody: false, 23 | child: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [Text('Home')])), 24 | ), 25 | ], 26 | ), 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/feature/initialization/data/app_migrator.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_classes_with_only_static_members 2 | 3 | import 'dart:async'; 4 | import 'dart:math'; 5 | 6 | import 'package:flutter_template_name/src/common/constant/config.dart'; 7 | import 'package:flutter_template_name/src/common/constant/pubspec.yaml.g.dart'; 8 | import 'package:flutter_template_name/src/common/database/database.dart'; 9 | import 'package:l/l.dart'; 10 | 11 | /// Migrate application when version is changed. 12 | sealed class AppMigrator { 13 | static Future migrate(Database database) async { 14 | try { 15 | final prevMajor = database.getKey(Config.versionMajorKey); 16 | final prevMinor = database.getKey(Config.versionMinorKey); 17 | final prevPatch = database.getKey(Config.versionPatchKey); 18 | if (prevMajor == null || prevMinor == null || prevPatch == null) { 19 | l.i('Initializing app for the first time'); 20 | /* ... */ 21 | } else if (Pubspec.version.major != prevMajor || 22 | Pubspec.version.minor != prevMinor || 23 | Pubspec.version.patch != prevPatch) { 24 | l.i( 25 | 'Migrating from $prevMajor.$prevMinor.$prevPatch ' 26 | 'to ' 27 | '${Pubspec.version.major}.${Pubspec.version.minor}.${Pubspec.version.patch}', 28 | ); 29 | /* ... */ 30 | } else { 31 | l.i('App is up-to-date'); 32 | return; 33 | } 34 | database.setAll({ 35 | Config.versionMajorKey: Pubspec.version.major, 36 | Config.versionMinorKey: Pubspec.version.minor, 37 | Config.versionPatchKey: Pubspec.version.patch, 38 | }); 39 | } on Object catch (error, stackTrace) { 40 | l.e('App migration failed: $e', stackTrace); 41 | rethrow; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/src/feature/initialization/data/platform/platform_initialization.dart: -------------------------------------------------------------------------------- 1 | export 'platform_initialization_vm.dart' 2 | // ignore: uri_does_not_exist 3 | if (dart.library.html) 'platform_initialization_js.dart'; 4 | -------------------------------------------------------------------------------- /lib/src/feature/initialization/data/platform/platform_initialization_js.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_web_libraries_in_flutter 2 | 3 | import 'package:web/web.dart'; 4 | 5 | Future $platformInitialization() async { 6 | // setUrlStrategy(const HashUrlStrategy()); 7 | 8 | // Remove splash screen 9 | Future.delayed(const Duration(seconds: 1), () { 10 | // Before running your app: 11 | // setUrlStrategy(null); // const HashUrlStrategy(); 12 | // setUrlStrategy(NoHistoryUrlStrategy()); 13 | 14 | document.getElementById('splash')?.remove(); 15 | document.getElementById('splash-branding')?.remove(); 16 | document.body?.style.background = 'transparent'; 17 | 18 | final elements = document.getElementsByClassName('splash-loading'); 19 | for (var i = elements.length - 1; i >= 0; i--) elements.item(i)?.remove(); 20 | }); 21 | } 22 | 23 | /* class NoHistoryUrlStrategy extends PathUrlStrategy { 24 | @override 25 | void pushState(Object? state, String title, String url) => replaceState(state, title, url); 26 | } 27 | */ 28 | -------------------------------------------------------------------------------- /lib/src/feature/initialization/data/platform/platform_initialization_vm.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io' as io; 2 | import 'dart:ui' as ui; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:window_manager/window_manager.dart'; 7 | 8 | Future $platformInitialization() => 9 | io.Platform.isAndroid || io.Platform.isIOS ? _mobileInitialization() : _desktopInitialization(); 10 | 11 | Future _mobileInitialization() async { 12 | // Set the app to be full-screen (no buttons, bar or notifications on top). 13 | await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); 14 | // Set the preferred orientation of the app to landscape only. 15 | await SystemChrome.setPreferredOrientations([DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]); 16 | } 17 | 18 | Future _desktopInitialization() async { 19 | final platform = ui.PlatformDispatcher.instance; 20 | // Must add this line. 21 | await windowManager.ensureInitialized(); 22 | final windowOptions = WindowOptions( 23 | minimumSize: const Size(320, 480), 24 | size: const Size(960, 800), 25 | /* maximumSize: const Size(1440, 1080), */ 26 | center: true, 27 | windowButtonVisibility: false, 28 | backgroundColor: 29 | platform.platformBrightness == Brightness.dark 30 | ? ThemeData.dark().colorScheme.surface 31 | : ThemeData.light().colorScheme.surface, 32 | skipTaskbar: false, 33 | titleBarStyle: TitleBarStyle.hidden, 34 | alwaysOnTop: false, 35 | fullScreen: false, 36 | title: 'Application', 37 | ); 38 | await windowManager.waitUntilReadyToShow(windowOptions, () async { 39 | if (io.Platform.isMacOS) { 40 | await windowManager.setMovable(true); 41 | } 42 | await windowManager.setMaximizable(true); 43 | await windowManager.show(); 44 | await windowManager.focus(); 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /lib/src/feature/initialization/widget/initialization_splash_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:ui/ui.dart'; 3 | 4 | class InitializationSplashScreen extends StatelessWidget { 5 | const InitializationSplashScreen({required this.progress, super.key}); 6 | 7 | final ValueListenable<({int progress, String message})> progress; 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | final theme = 12 | View.of(context).platformDispatcher.platformBrightness == Brightness.dark 13 | ? ThemeData.dark() 14 | : ThemeData.light(); 15 | return Material( 16 | color: theme.primaryColor, 17 | child: Directionality( 18 | textDirection: TextDirection.ltr, 19 | child: Center( 20 | child: ListView( 21 | shrinkWrap: true, 22 | children: [ 23 | RadialProgressIndicator( 24 | size: 128, 25 | child: ValueListenableBuilder<({String message, int progress})>( 26 | valueListenable: progress, 27 | builder: 28 | (context, value, _) => Text( 29 | '${value.progress}%', 30 | overflow: TextOverflow.ellipsis, 31 | maxLines: 1, 32 | textAlign: TextAlign.center, 33 | style: theme.textTheme.titleLarge?.copyWith(height: 1, fontSize: 32), 34 | ), 35 | ), 36 | ), 37 | const SizedBox(height: 16), 38 | Opacity( 39 | opacity: .25, 40 | child: ValueListenableBuilder<({String message, int progress})>( 41 | valueListenable: progress, 42 | builder: 43 | (context, value, _) => Text( 44 | value.message, 45 | overflow: TextOverflow.ellipsis, 46 | maxLines: 3, 47 | textAlign: TextAlign.center, 48 | style: theme.textTheme.labelSmall?.copyWith(height: 1), 49 | ), 50 | ), 51 | ), 52 | ], 53 | ), 54 | ), 55 | ), 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/src/feature/settings/widget/settings_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// {@template settings_screen} 4 | /// SettingsScreen widget. 5 | /// {@endtemplate} 6 | class SettingsDialog extends StatelessWidget { 7 | /// {@macro settings_screen} 8 | const SettingsDialog({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) => AlertDialog.adaptive( 12 | title: const Text('Settings'), 13 | content: const Text('Coming soon...'), 14 | actions: [TextButton(onPressed: () => Navigator.maybePop(context), child: const Text('Close'))], 15 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/feature/settings/widget/settings_icon_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:flutter_template_name/src/common/localization/localization.dart'; 4 | import 'package:flutter_template_name/src/common/router/routes.dart'; 5 | import 'package:octopus/octopus.dart'; 6 | 7 | /// {@template profile_icon_button} 8 | /// ProfileIconButton widget 9 | /// {@endtemplate} 10 | class SettingsIconButton extends StatelessWidget { 11 | /// {@macro profile_icon_button} 12 | const SettingsIconButton({super.key}); 13 | 14 | @override 15 | Widget build(BuildContext context) => IconButton( 16 | icon: const Icon(Icons.person), 17 | tooltip: Localization.of(context).profileButton, 18 | onPressed: () { 19 | Octopus.maybeOf(context)?.setState( 20 | (state) => 21 | state 22 | ..removeByName(Routes.settings.name) 23 | ..add(Routes.settings.node()), 24 | ); 25 | HapticFeedback.mediumImpact().ignore(); 26 | }, 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | void fl_register_plugins(FlPluginRegistry* registry) { 15 | g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar = 16 | fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin"); 17 | screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar); 18 | g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar = 19 | fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin"); 20 | sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar); 21 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 22 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 23 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 24 | g_autoptr(FlPluginRegistrar) window_manager_registrar = 25 | fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); 26 | window_manager_plugin_register_with_registrar(window_manager_registrar); 27 | } 28 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | screen_retriever_linux 7 | sqlite3_flutter_libs 8 | url_launcher_linux 9 | window_manager 10 | ) 11 | 12 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 13 | ) 14 | 15 | set(PLUGIN_BUNDLED_LIBRARIES) 16 | 17 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 18 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 19 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 22 | endforeach(plugin) 23 | 24 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 25 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 27 | endforeach(ffi_plugin) 28 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /linux/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} 10 | "main.cc" 11 | "my_application.cc" 12 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 13 | ) 14 | 15 | # Apply the standard set of build settings. This can be removed for applications 16 | # that need different build settings. 17 | apply_standard_settings(${BINARY_NAME}) 18 | 19 | # Add preprocessor definitions for the application ID. 20 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") 21 | 22 | # Add dependency libraries. Add any application-specific dependencies here. 23 | target_link_libraries(${BINARY_NAME} PRIVATE flutter) 24 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) 25 | 26 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 27 | -------------------------------------------------------------------------------- /linux/runner/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /linux/runner/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import path_provider_foundation 9 | import screen_retriever_macos 10 | import shared_preferences_foundation 11 | import sqlite3_flutter_libs 12 | import url_launcher_macos 13 | import window_manager 14 | 15 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 16 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 17 | ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) 18 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) 19 | Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) 20 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 21 | WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) 22 | } 23 | -------------------------------------------------------------------------------- /macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.14' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | target 'RunnerTests' do 35 | inherit! :search_paths 36 | end 37 | end 38 | 39 | post_install do |installer| 40 | installer.pods_project.targets.each do |target| 41 | flutter_additional_macos_build_settings(target) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /macos/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - FlutterMacOS (1.0.0) 3 | - path_provider_foundation (0.0.1): 4 | - Flutter 5 | - FlutterMacOS 6 | - screen_retriever_macos (0.0.1): 7 | - FlutterMacOS 8 | - shared_preferences_foundation (0.0.1): 9 | - Flutter 10 | - FlutterMacOS 11 | - sqlite3 (3.49.0): 12 | - sqlite3/common (= 3.49.0) 13 | - sqlite3/common (3.49.0) 14 | - sqlite3/dbstatvtab (3.49.0): 15 | - sqlite3/common 16 | - sqlite3/fts5 (3.49.0): 17 | - sqlite3/common 18 | - sqlite3/perf-threadsafe (3.49.0): 19 | - sqlite3/common 20 | - sqlite3/rtree (3.49.0): 21 | - sqlite3/common 22 | - sqlite3_flutter_libs (0.0.1): 23 | - Flutter 24 | - FlutterMacOS 25 | - sqlite3 (~> 3.49.0) 26 | - sqlite3/dbstatvtab 27 | - sqlite3/fts5 28 | - sqlite3/perf-threadsafe 29 | - sqlite3/rtree 30 | - url_launcher_macos (0.0.1): 31 | - FlutterMacOS 32 | - window_manager (0.2.0): 33 | - FlutterMacOS 34 | 35 | DEPENDENCIES: 36 | - FlutterMacOS (from `Flutter/ephemeral`) 37 | - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) 38 | - screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`) 39 | - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) 40 | - sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`) 41 | - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) 42 | - window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`) 43 | 44 | SPEC REPOS: 45 | trunk: 46 | - sqlite3 47 | 48 | EXTERNAL SOURCES: 49 | FlutterMacOS: 50 | :path: Flutter/ephemeral 51 | path_provider_foundation: 52 | :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin 53 | screen_retriever_macos: 54 | :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos 55 | shared_preferences_foundation: 56 | :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin 57 | sqlite3_flutter_libs: 58 | :path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin 59 | url_launcher_macos: 60 | :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos 61 | window_manager: 62 | :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos 63 | 64 | SPEC CHECKSUMS: 65 | FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 66 | path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 67 | screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f 68 | shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 69 | sqlite3: 4922312598b67e1825c6a6c821296dcbf6783046 70 | sqlite3_flutter_libs: 3c323550ef3b928bc0aa9513c841e45a7d242832 71 | url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 72 | window_manager: 1d01fa7ac65a6e6f83b965471b1a7fdd3f06166c 73 | 74 | PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 75 | 76 | COCOAPODS: 1.16.2 77 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @main 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | 10 | override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { 11 | return true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = flutter_template_name 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.template.flutterTemplateName 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2025 dev.flutter.template. All rights reserved. 15 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 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 | -------------------------------------------------------------------------------- /packages/ui/lib/shaders/shimmer.frag: -------------------------------------------------------------------------------- 1 | #version 460 core 2 | #define SHOW_GRID 3 | 4 | #include 5 | 6 | uniform vec2 uSize; // size of the shape 7 | uniform float uSeed; // shader playback time (in seconds) 8 | uniform vec4 uLineColor; // line color of the shape 9 | uniform vec4 uBackgroundColor; // background color of the shape 10 | uniform float uStripeWidth; // width of the stripes 11 | 12 | out vec4 fragColor; 13 | 14 | void main() { 15 | // Direction vector for 30 degrees angle (values are precalculated) 16 | vec2 direction = vec2(0.866, 0.5); 17 | 18 | // Calculate normalized coordinates 19 | vec2 normalizedCoords = gl_FragCoord.xy / uSize; 20 | 21 | // Generate a smooth moving wave based on time and coordinates 22 | float waveRaw = 0.5 * (1.0 + sin(uSeed - dot(normalizedCoords, direction) * uStripeWidth * 3.1415)); 23 | float wave = smoothstep(0.0, 1.0, waveRaw); 24 | 25 | // Use the wave to interpolate between the background color and line color 26 | vec4 color = mix(uBackgroundColor, uLineColor, wave); 27 | 28 | fragColor = color; 29 | } -------------------------------------------------------------------------------- /packages/ui/lib/src/components/charts/charts.dart: -------------------------------------------------------------------------------- 1 | export 'package:ui/src/components/charts/pie_chart.dart'; 2 | -------------------------------------------------------------------------------- /packages/ui/lib/src/components/components.dart: -------------------------------------------------------------------------------- 1 | export 'package:ui/src/components/charts/charts.dart'; 2 | export 'package:ui/src/components/layout/layout.dart'; 3 | export 'package:ui/src/components/placeholders/placeholders.dart'; 4 | -------------------------------------------------------------------------------- /packages/ui/lib/src/components/layout/layout.dart: -------------------------------------------------------------------------------- 1 | export 'package:ui/src/components/layout/popup.dart'; 2 | -------------------------------------------------------------------------------- /packages/ui/lib/src/components/placeholders/form_placeholder.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:ui/src/components/placeholders/shimmer.dart'; 3 | import 'package:ui/src/components/placeholders/text_placeholder.dart'; 4 | 5 | /// {@template form_placeholder} 6 | /// FormPlaceholder widget. 7 | /// {@endtemplate} 8 | class FormPlaceholder extends StatelessWidget { 9 | /// {@macro form_placeholder} 10 | const FormPlaceholder({this.title = false, super.key}); 11 | 12 | final bool title; 13 | 14 | @override 15 | Widget build(BuildContext context) => Column( 16 | children: [ 17 | if (title) 18 | Padding( 19 | padding: const EdgeInsets.all(8), 20 | child: Shimmer( 21 | size: const Size(64, 64), 22 | color: Colors.grey[400], 23 | backgroundColor: Colors.grey[100], 24 | cornerRadius: 24, 25 | ), 26 | ), 27 | const SizedBox(height: 16), 28 | const TextPlaceholder(width: 152), 29 | const SizedBox(height: 16), 30 | const TextPlaceholder(width: 256), 31 | const SizedBox(height: 16), 32 | const TextPlaceholder(width: 128), 33 | const SizedBox(height: 16), 34 | const TextPlaceholder(width: 64), 35 | const SizedBox(height: 16), 36 | const TextPlaceholder(width: 256), 37 | const SizedBox(height: 16), 38 | const TextPlaceholder(width: 512), 39 | const SizedBox(height: 16), 40 | const TextPlaceholder(width: 256), 41 | const SizedBox(height: 16), 42 | const TextPlaceholder(width: 128), 43 | ], 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /packages/ui/lib/src/components/placeholders/placeholders.dart: -------------------------------------------------------------------------------- 1 | export 'package:ui/src/components/placeholders/form_placeholder.dart'; 2 | export 'package:ui/src/components/placeholders/radial_progress_indicator.dart'; 3 | export 'package:ui/src/components/placeholders/shimmer.dart'; 4 | export 'package:ui/src/components/placeholders/text_placeholder.dart'; 5 | -------------------------------------------------------------------------------- /packages/ui/lib/src/components/placeholders/text_placeholder.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:ui/src/components/placeholders/shimmer.dart'; 5 | 6 | /// {@template text_placeholder} 7 | /// TextPlaceholder widget. 8 | /// {@endtemplate} 9 | class TextPlaceholder extends StatelessWidget { 10 | /// {@macro text_placeholder} 11 | const TextPlaceholder({ 12 | this.width = double.infinity, 13 | this.height = 28, 14 | this.padding = const EdgeInsets.symmetric(horizontal: 16), 15 | super.key, 16 | }); 17 | 18 | /// Size of the placeholder 19 | final double width; 20 | final double height; 21 | final EdgeInsetsGeometry padding; 22 | 23 | @override 24 | Widget build(BuildContext context) => Padding( 25 | padding: padding, 26 | child: LayoutBuilder( 27 | builder: 28 | (context, constraints) => Shimmer( 29 | alignment: Alignment.centerLeft, 30 | size: Size(math.min(width, constraints.maxWidth), height), 31 | color: Colors.grey[400], 32 | backgroundColor: Colors.grey[100], 33 | ), 34 | ), 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /packages/ui/lib/src/theme/extensions/colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// {@template app_colors} 4 | /// Emphasis class 5 | /// {@endtemplate} 6 | @immutable 7 | class AppColors implements ThemeExtension { 8 | /// {@macro app_colors} 9 | const AppColors({required this.scheme}); 10 | 11 | factory AppColors.of(BuildContext context) { 12 | try { 13 | final theme = Theme.of(context); 14 | return theme.extension() ?? 15 | switch (theme.brightness) { 16 | Brightness.light => AppColors.light, 17 | Brightness.dark => AppColors.dark, 18 | }; 19 | } on Object { 20 | return AppColors.light; 21 | } 22 | } 23 | 24 | /// The default light theme colors. 25 | /// 26 | /// {@macro app_colors} 27 | static const AppColors light = AppColors(scheme: ColorScheme.light()); 28 | 29 | /// The default dark theme colors. 30 | /// 31 | /// {@macro app_colors} 32 | static const AppColors dark = AppColors(scheme: ColorScheme.dark()); 33 | 34 | /// The color scheme of the [AppColors]. 35 | final ColorScheme scheme; 36 | 37 | @override 38 | Object get type => AppColors; 39 | 40 | /// Returns `true` if the brightness is [Brightness.light]. 41 | bool get isLight => scheme.brightness == Brightness.light; 42 | 43 | /// Returns `true` if the brightness is [Brightness.dark]. 44 | bool get isDark => scheme.brightness == Brightness.dark; 45 | 46 | /// Returns [ThemeMode] of the closest [Theme] ancestor. 47 | /// 48 | /// {@macro app_colors} 49 | static ThemeMode themeModeOf(BuildContext context) { 50 | final theme = Theme.of(context); 51 | return switch (theme.brightness) { 52 | Brightness.light => ThemeMode.light, 53 | Brightness.dark => ThemeMode.dark, 54 | }; 55 | } 56 | 57 | /// Pattern matching on the brightness of the [AppColors]. 58 | T map({required T Function() light, required T Function() dark}) => isLight ? light() : dark(); 59 | 60 | @override 61 | ThemeExtension copyWith({ColorScheme? scheme}) => AppColors(scheme: scheme ?? this.scheme); 62 | 63 | @override 64 | ThemeExtension lerp(covariant ThemeExtension? other, double t) { 65 | if (other is! AppColors) return this; 66 | return AppColors(scheme: ColorScheme.lerp(scheme, other.scheme, t)); 67 | } 68 | 69 | @override 70 | String toString() => 'AppColors{}'; 71 | } 72 | -------------------------------------------------------------------------------- /packages/ui/lib/src/theme/theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:ui/src/theme/extensions/colors.dart'; 3 | 4 | export 'package:ui/src/theme/extensions/colors.dart'; 5 | 6 | /// {@template theme} 7 | /// App theme data. 8 | /// {@endtemplate} 9 | extension type AppThemeData._(ThemeData data) implements ThemeData { 10 | /// {@macro theme} 11 | factory AppThemeData.light() => AppThemeData._(_appLightTheme); 12 | 13 | /// {@macro theme} 14 | factory AppThemeData.dark() => AppThemeData._(_appDarkTheme); 15 | } 16 | 17 | /// Extension on [ThemeData] to provide App theme data. 18 | extension AudoThemeExtension on ThemeData { 19 | /// Returns the App theme colors. 20 | AppColors get appColors => 21 | extension() ?? 22 | switch (brightness) { 23 | Brightness.light => AppColors.light, 24 | Brightness.dark => AppColors.dark, 25 | }; 26 | } 27 | 28 | // --- Light Theme --- // 29 | 30 | /// Light theme data for the App. 31 | final ThemeData _appLightTheme = ThemeData.light().copyWith( 32 | colorScheme: AppColors.light.scheme, 33 | extensions: const [AppColors.light], 34 | ); 35 | 36 | // --- Dark Theme --- // 37 | 38 | /// Dark theme data for the App. 39 | final ThemeData _appDarkTheme = ThemeData.dark().copyWith( 40 | colorScheme: AppColors.dark.scheme, 41 | extensions: const [AppColors.dark], 42 | ); 43 | -------------------------------------------------------------------------------- /packages/ui/lib/ui.dart: -------------------------------------------------------------------------------- 1 | library; 2 | 3 | export 'package:flutter/material.dart'; 4 | export 'package:ui/src/components/components.dart'; 5 | export 'package:ui/src/theme/theme.dart'; 6 | -------------------------------------------------------------------------------- /packages/ui/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ui 2 | description: Shared UI Components, Widgets, Fonts, Colors and Theme 3 | publish_to: 'none' 4 | version: 1.0.0 5 | 6 | environment: 7 | sdk: '>=3.7.0 <4.0.0' 8 | flutter: ">=3.29.0" 9 | 10 | # https://dart.dev/tools/pub/workspaces 11 | resolution: workspace 12 | 13 | dependencies: 14 | flutter: 15 | sdk: flutter 16 | intl: 17 | collection: 18 | #google_fonts: 19 | 20 | dev_dependencies: 21 | # Test 22 | flutter_test: 23 | sdk: flutter 24 | 25 | flutter: 26 | uses-material-design: true 27 | -------------------------------------------------------------------------------- /test/unit_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_template_name/src/common/util/date_util.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() => group('Unit', () { 5 | group('DateUtil', () { 6 | test('DateTime_to_String', () { 7 | expect( 8 | DateTime(2024, 10, 27, 13, 45, 59).toLocalIso8601String(), 9 | allOf(isNotNull, isNotEmpty, startsWith('2024-10-27T13:45:59'), isNot(endsWith('59')), isNot(endsWith('Z'))), 10 | ); 11 | 12 | expect( 13 | DateTime.utc(2024, 10, 27, 13, 45, 59).toUtcIso8601String(), 14 | allOf(isNotNull, isNotEmpty, equals('2024-10-27T13:45:59Z'), isNot(contains('+')), endsWith('Z')), 15 | ); 16 | }); 17 | 18 | test('String_to_DateTime', () { 19 | expect( 20 | DateTime.parse('2024-10-27T13:45:59+02:00'), 21 | isA() 22 | .having((it) => it.year, 'year', equals(2024)) 23 | .having((it) => it.month, 'month', equals(10)) 24 | .having((it) => it.minute, 'minute', equals(45)) 25 | .having((it) => it.second, 'second', equals(59)), 26 | ); 27 | 28 | expect(DateTime.parse('2024-10-27T13:45:59Z'), equals(DateTime.utc(2024, 10, 27, 13, 45, 59))); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_template_name/src/common/model/dependencies.dart'; 3 | import 'package:flutter_template_name/src/common/widget/app.dart'; 4 | import 'package:flutter_template_name/src/feature/authentication/controller/authentication_controller.dart'; 5 | import 'package:flutter_template_name/src/feature/authentication/data/authentication_repository.dart'; 6 | import 'package:flutter_template_name/src/feature/settings/widget/settings_scope.dart'; 7 | import 'package:flutter_test/flutter_test.dart'; 8 | import 'package:octopus/octopus.dart'; 9 | 10 | void main() => group('Widget', () { 11 | testWidgets('Dependencies_are_injected', (tester) async { 12 | await tester.pumpWidget(FakeDependencies().inject(child: Container())); 13 | expect(find.byType(Container), findsOneWidget); 14 | expect(find.byType(InheritedDependencies), findsOneWidget); 15 | final context = tester.element(find.byType(Container)); 16 | expect(Dependencies.of(context), allOf(isNotNull, isA(), isA())); 17 | }); 18 | 19 | testWidgets('App', (tester) async { 20 | final dependencies = 21 | FakeDependencies() 22 | ..authenticationController = AuthenticationController(repository: AuthenticationRepositoryFake()); 23 | await tester.pumpWidget( 24 | dependencies.inject(child: const SettingsScope(child: NoAnimationScope(noAnimation: true, child: App()))), 25 | ); 26 | expect(find.byType(MaterialApp), findsOneWidget); 27 | expect(find.byType(InheritedDependencies), findsOneWidget); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /web/AppIcon~ios-marketing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/AppIcon~ios-marketing.png -------------------------------------------------------------------------------- /web/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/android-chrome-192x192.png -------------------------------------------------------------------------------- /web/android-chrome-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/android-chrome-384x384.png -------------------------------------------------------------------------------- /web/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/apple-touch-icon.png -------------------------------------------------------------------------------- /web/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | #37474f 10 | 11 | 12 | -------------------------------------------------------------------------------- /web/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/favicon-16x16.png -------------------------------------------------------------------------------- /web/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/favicon-32x32.png -------------------------------------------------------------------------------- /web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/favicon.ico -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/favicon.png -------------------------------------------------------------------------------- /web/icon-192-maskable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/icon-192-maskable.png -------------------------------------------------------------------------------- /web/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/icon-192.png -------------------------------------------------------------------------------- /web/icon-2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/icon-2048.png -------------------------------------------------------------------------------- /web/icon-500-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/icon-500-transparent.png -------------------------------------------------------------------------------- /web/icon-512-maskable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/icon-512-maskable.png -------------------------------------------------------------------------------- /web/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | flutter_template_name 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flutter_template_name", 3 | "short_name": "flutter_template_name", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "flutter_template_description", 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 | -------------------------------------------------------------------------------- /web/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/mstile-150x150.png -------------------------------------------------------------------------------- /web/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/mstile-310x150.png -------------------------------------------------------------------------------- /web/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/mstile-310x310.png -------------------------------------------------------------------------------- /web/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/mstile-70x70.png -------------------------------------------------------------------------------- /web/play_store_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/play_store_512.png -------------------------------------------------------------------------------- /web/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.14, written by Peter Selinger 2001-2017 9 | 10 | 12 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /web/splash/img/dark-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/splash/img/dark-1x.png -------------------------------------------------------------------------------- /web/splash/img/dark-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/splash/img/dark-2x.png -------------------------------------------------------------------------------- /web/splash/img/dark-3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/splash/img/dark-3x.png -------------------------------------------------------------------------------- /web/splash/img/dark-4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/splash/img/dark-4x.png -------------------------------------------------------------------------------- /web/splash/img/light-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/splash/img/light-1x.png -------------------------------------------------------------------------------- /web/splash/img/light-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/splash/img/light-2x.png -------------------------------------------------------------------------------- /web/splash/img/light-3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/splash/img/light-3x.png -------------------------------------------------------------------------------- /web/splash/img/light-4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/splash/img/light-4x.png -------------------------------------------------------------------------------- /web/sqlite3.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/web/sqlite3.wasm -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | void RegisterPlugins(flutter::PluginRegistry* registry) { 15 | ScreenRetrieverWindowsPluginCApiRegisterWithRegistrar( 16 | registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi")); 17 | Sqlite3FlutterLibsPluginRegisterWithRegistrar( 18 | registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); 19 | UrlLauncherWindowsRegisterWithRegistrar( 20 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 21 | WindowManagerPluginRegisterWithRegistrar( 22 | registry->GetRegistrarForPlugin("WindowManagerPlugin")); 23 | } 24 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | screen_retriever_windows 7 | sqlite3_flutter_libs 8 | url_launcher_windows 9 | window_manager 10 | ) 11 | 12 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 13 | ) 14 | 15 | set(PLUGIN_BUNDLED_LIBRARIES) 16 | 17 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 18 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 19 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 22 | endforeach(plugin) 23 | 24 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 25 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 27 | endforeach(ffi_plugin) 28 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 38 | 39 | # Run the Flutter tool portions of the build. This must not be removed. 40 | add_dependencies(${BINARY_NAME} flutter_assemble) 41 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | 30 | flutter_controller_->engine()->SetNextFrameCallback([&]() { 31 | this->Show(); 32 | }); 33 | 34 | // Flutter can complete the first frame before the "show window" callback is 35 | // registered. The following call ensures a frame is pending to ensure the 36 | // window is shown. It is a no-op if the first frame hasn't completed yet. 37 | flutter_controller_->ForceRedraw(); 38 | 39 | return true; 40 | } 41 | 42 | void FlutterWindow::OnDestroy() { 43 | if (flutter_controller_) { 44 | flutter_controller_ = nullptr; 45 | } 46 | 47 | Win32Window::OnDestroy(); 48 | } 49 | 50 | LRESULT 51 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 52 | WPARAM const wparam, 53 | LPARAM const lparam) noexcept { 54 | // Give Flutter, including plugins, an opportunity to handle window messages. 55 | if (flutter_controller_) { 56 | std::optional result = 57 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 58 | lparam); 59 | if (result) { 60 | return *result; 61 | } 62 | } 63 | 64 | switch (message) { 65 | case WM_FONTCHANGE: 66 | flutter_controller_->engine()->ReloadSystemFonts(); 67 | break; 68 | } 69 | 70 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 71 | } 72 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.Create(L"flutter_template_name", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlugFox/template/6108f957d20adf90428f093fbbf6c4b2b78fc046/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | unsigned int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr) 51 | -1; // remove the trailing null character 52 | int input_length = (int)wcslen(utf16_string); 53 | std::string utf8_string; 54 | if (target_length == 0 || target_length > utf8_string.max_size()) { 55 | return utf8_string; 56 | } 57 | utf8_string.resize(target_length); 58 | int converted_length = ::WideCharToMultiByte( 59 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 60 | input_length, utf8_string.data(), target_length, nullptr, nullptr); 61 | if (converted_length == 0) { 62 | return std::string(); 63 | } 64 | return utf8_string; 65 | } 66 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | --------------------------------------------------------------------------------