├── .github ├── actions │ ├── android │ │ └── action.yml │ ├── common │ │ └── action.yml │ └── ios │ │ └── action.yml └── workflows │ ├── pull_request.yml │ └── push.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── org │ │ │ │ └── fossasia │ │ │ │ └── magic_epaper_app │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── build │ └── .last_build_id ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── canvas │ ├── black.png │ ├── red.png │ └── white.png └── images │ ├── 2.0x │ └── flutter_logo.png │ ├── 3.0x │ └── flutter_logo.png │ ├── FOSSASIA-rotated.svg │ ├── FOSSASIA.svg │ ├── black-red.png │ ├── displays │ ├── epaper_3.7_bw.PNG │ └── epaper_3.7_bwr.png │ ├── h-flip.png │ └── tux-fit.png ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h └── RunnerTests │ └── RunnerTests.swift ├── l10n.yaml ├── lib ├── constants │ ├── asset_paths.dart │ ├── color_constants.dart │ └── string_constants.dart ├── main.dart ├── pro_image_editor │ ├── core │ │ ├── constants │ │ │ └── example_constants.dart │ │ ├── mixin │ │ │ └── example_helper.dart │ │ └── models │ │ │ └── example_model.dart │ ├── features │ │ ├── bottom_bar.dart │ │ ├── color_picker.dart │ │ ├── movable_background_image.dart │ │ ├── preview │ │ │ └── preview_img.dart │ │ ├── reorder_layer_example.dart │ │ └── text_bottom_bar.dart │ └── shared │ │ └── widgets │ │ ├── demo_build_stickers.dart │ │ ├── material_icon_button.dart │ │ ├── not_found_example.dart │ │ ├── paragraph_info_widget.dart │ │ ├── pixel_transparent_painter.dart │ │ └── prepare_image_widget.dart ├── provider │ ├── color_palette_provider.dart │ ├── getitlocator.dart │ └── image_loader.dart ├── src │ └── localization │ │ └── app_en.arb ├── util │ ├── epd │ │ ├── driver │ │ │ ├── driver.dart │ │ │ └── uc8253.dart │ │ ├── epd.dart │ │ ├── gdey037z03.dart │ │ └── gdey037z03bw.dart │ ├── image_editor_utils.dart │ ├── image_processing │ │ ├── extract_quantizer.dart │ │ ├── image_processing.dart │ │ └── remap_quantizer.dart │ ├── magic_epaper_firmware.dart │ ├── nfc_settings_launcher.dart │ ├── protocol.dart │ └── st25dv.dart └── view │ ├── display_selection_screen.dart │ ├── image_editor.dart │ └── widget │ ├── color_dot.dart │ ├── display_card.dart │ ├── flip_controls.dart │ └── image_list.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 ├── macos ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── 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 ├── pubspec.lock ├── pubspec.yaml ├── test ├── unit_test.dart └── widget_test.dart ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── index.html └── manifest.json └── 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 /.github/actions/android/action.yml: -------------------------------------------------------------------------------- 1 | name: "Android Workflow" 2 | 3 | inputs: 4 | STORE_PASS: 5 | description: 'Store Password' 6 | required: false 7 | default: '' 8 | ALIAS: 9 | description: 'Certificate Alias' 10 | required: false 11 | default: '' 12 | KEY_PASS: 13 | description: 'Key Password' 14 | required: false 15 | default: '' 16 | VERSION_NAME: 17 | description: 'Version Name to be used for build' 18 | required: false 19 | default: '1.0.0' 20 | VERSION_CODE: 21 | description: 'Version Code to be used for build' 22 | required: true 23 | default: '1' 24 | 25 | runs: 26 | using: "composite" 27 | steps: 28 | - name: Set up Java 29 | uses: actions/setup-java@v4 30 | with: 31 | java-version: 17 32 | distribution: 'adopt' 33 | cache: 'gradle' 34 | 35 | - name: Set up Flutter 36 | uses: subosito/flutter-action@v2 37 | with: 38 | cache: true 39 | flutter-version-file: pubspec.yaml 40 | 41 | - name: Build Android APK/AAB 42 | shell: bash 43 | env: 44 | STORE_PASS: ${{ inputs.STORE_PASS }} 45 | ALIAS: ${{ inputs.ALIAS }} 46 | KEY_PASS: ${{ inputs.KEY_PASS }} 47 | VERSION_NAME: ${{inputs.VERSION_NAME}} 48 | VERSION_CODE: ${{inputs.VERSION_CODE}} 49 | run: | 50 | flutter build apk --debug --build-name $VERSION_NAME --build-number $VERSION_CODE 51 | flutter build apk --build-name $VERSION_NAME --build-number $VERSION_CODE 52 | flutter build appbundle --build-name $VERSION_NAME --build-number $VERSION_CODE 53 | 54 | - name: Store APK file 55 | uses: actions/upload-artifact@v4 56 | with: 57 | name: apk-files 58 | path: | 59 | build/app/outputs/flutter-apk/app-debug.apk -------------------------------------------------------------------------------- /.github/actions/common/action.yml: -------------------------------------------------------------------------------- 1 | name: "Common Workflow" 2 | 3 | runs: 4 | using: "composite" 5 | steps: 6 | - name: Set up Flutter 7 | uses: subosito/flutter-action@v2 8 | with: 9 | cache: true 10 | flutter-version-file: pubspec.yaml 11 | 12 | - name: Fetch Flutter Dependencies 13 | shell: bash 14 | run: flutter pub get 15 | 16 | - name: Validate Code Format 17 | shell: bash 18 | run: dart format --output=none --set-exit-if-changed . 19 | 20 | - name: Analyze Code 21 | shell: bash 22 | run: flutter analyze --no-fatal-infos 23 | 24 | - name: Run tests 25 | shell: bash 26 | run: flutter test 27 | -------------------------------------------------------------------------------- /.github/actions/ios/action.yml: -------------------------------------------------------------------------------- 1 | name: "iOS Workflow" 2 | 3 | inputs: 4 | VERSION_NAME: 5 | description: 'Version Name to be used for build' 6 | required: false 7 | default: '1.0.0' 8 | VERSION_CODE: 9 | description: 'Version Code to be used for build' 10 | required: true 11 | default: '1' 12 | 13 | runs: 14 | using: "composite" 15 | steps: 16 | - name: Set up Flutter 17 | uses: subosito/flutter-action@v2 18 | with: 19 | cache: true 20 | flutter-version-file: pubspec.yaml 21 | 22 | - name: Update Podfile 23 | shell: bash 24 | run: | 25 | cd ./iOS 26 | flutter pub get 27 | pod install --repo-update 28 | 29 | - name: Build iOS IPA (No Code Signing for PRs) 30 | if: ${{ github.event_name == 'pull_request' }} 31 | shell: bash 32 | env: 33 | VERSION_NAME: ${{ inputs.VERSION_NAME }} 34 | VERSION_CODE: ${{ inputs.VERSION_CODE }} 35 | run: | 36 | flutter build ipa --no-codesign --build-name $VERSION_NAME --build-number $VERSION_CODE 37 | 38 | - name: Build iOS IPA (With Code Signing) 39 | if: ${{ github.event_name == 'push' }} 40 | shell: bash 41 | env: 42 | VERSION_NAME: ${{ inputs.VERSION_NAME }} 43 | VERSION_CODE: ${{ inputs.VERSION_CODE }} 44 | run: | 45 | flutter build ipa --build-name $VERSION_NAME --build-number $VERSION_CODE 46 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: Magic Epaper PR CI 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | common: 10 | name: Common Build 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Common Workflow 16 | uses: ./.github/actions/common 17 | 18 | android: 19 | name: Android Flutter Build 20 | needs: common 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - name: Android Workflow 26 | uses: ./.github/actions/android 27 | 28 | ios: 29 | name: iOS Flutter Build 30 | needs: common 31 | runs-on: macos-latest 32 | steps: 33 | - uses: actions/checkout@v4 34 | 35 | - name: iOS Workflow 36 | uses: ./.github/actions/ios -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | name: Magic Epaper Push CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | env: 9 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 10 | ANDROID_EMULATOR_API: 34 11 | ANDROID_EMULATOR_ARCH: x86_64 12 | IPHONE_DEVICE_MODEL: iPhone 16 Pro Max 13 | IPAD_DEVICE_MODEL: iPad Pro 13-inch (M4) 14 | 15 | jobs: 16 | common: 17 | name: Common Build 18 | runs-on: ubuntu-latest 19 | outputs: 20 | VERSION_NAME: ${{ steps.flutter-version.outputs.VERSION_NAME }} 21 | VERSION_CODE: ${{ steps.flutter-version.outputs.VERSION_CODE }} 22 | steps: 23 | - name: Checkout main 24 | uses: actions/checkout@v4 25 | 26 | - name: Common Workflow 27 | uses: ./.github/actions/common 28 | 29 | - name: Hydrate and Update Version 30 | id: flutter-version 31 | run: | 32 | git config --global user.name "github-actions[bot]" 33 | git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" 34 | 35 | # Get commit message 36 | commit_message=$(git log -1 --pretty=format:"%s") 37 | 38 | git clone --branch=version https://${{ github.repository_owner }}:${{ github.token }}@github.com/${{ github.repository }} version 39 | cd version 40 | 41 | # Read and increment version name 42 | IFS='.' read -r major minor patch < versionName.txt 43 | 44 | if [[ "$commit_message" =~ ^feat: ]]; then 45 | next_minor=$((minor + 1)) 46 | next_patch=0 47 | else 48 | next_minor=$((minor)) 49 | next_patch=$((patch + 1)) 50 | fi 51 | next_version_name="$major.$next_minor.$next_patch" 52 | echo "VERSION_NAME=$next_version_name" >> $GITHUB_OUTPUT 53 | echo "$next_version_name" > versionName.txt 54 | 55 | # Read and increment version code 56 | read -r version_code < versionCode.txt 57 | 58 | new_version_code=$((version_code + 1)) 59 | echo "VERSION_CODE=$new_version_code" >> $GITHUB_OUTPUT 60 | echo "$new_version_code" > versionCode.txt 61 | 62 | # Force push to version branch 63 | git checkout --orphan temporary 64 | git add --all . 65 | git commit -am "[Auto] Update versionName: $next_version_name & versionCode: $new_version_code ($(date +%Y-%m-%d.%H:%M:%S))" 66 | git branch -D version 67 | git branch -m version 68 | git push --force origin version 69 | 70 | android: 71 | name: Android Flutter Build 72 | needs: common 73 | runs-on: ubuntu-latest 74 | steps: 75 | - uses: actions/checkout@v4 76 | 77 | - name: Prepare Build Keys 78 | if: ${{ github.repository == 'fossasia/magic-epaper-app' }} 79 | env: 80 | ENCRYPTED_F10B5E0E5262_IV: ${{ secrets.ENCRYPTED_F10B5E0E5262_IV }} 81 | ENCRYPTED_F10B5E0E5262_KEY: ${{ secrets.ENCRYPTED_F10B5E0E5262_KEY }} 82 | run: | 83 | bash scripts/prep-android-key.sh 84 | 85 | - name: Android Workflow 86 | uses: ./.github/actions/android 87 | with: 88 | STORE_PASS: ${{ secrets.STORE_PASS }} 89 | ALIAS: ${{ secrets.ALIAS }} 90 | KEY_PASS: ${{ secrets.KEY_PASS }} 91 | VERSION_NAME: ${{needs.common.outputs.VERSION_NAME}} 92 | VERSION_CODE: ${{needs.common.outputs.VERSION_CODE}} 93 | 94 | - name: Upload APK 95 | uses: actions/upload-artifact@v4 96 | with: 97 | name: APK Generated 98 | path: build/app/outputs/flutter-apk 99 | 100 | - name: Upload AAB Release 101 | uses: actions/upload-artifact@v4 102 | with: 103 | name: AAB Generated 104 | path: build/app/outputs/bundle 105 | 106 | - name: Upload APK/AAB to apk branch 107 | if: ${{ github.repository == 'fossasia/magic-epaper-app' }} 108 | run: | 109 | git config --global user.name "github-actions[bot]" 110 | git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" 111 | 112 | git clone --branch=apk https://${{ github.repository_owner }}:${{ github.token }}@github.com/${{ github.repository }} apk 113 | cd apk 114 | 115 | branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} 116 | 117 | echo "Removing previous files from branch" 118 | 119 | rm -rf magic-epaper-$branch* 120 | 121 | ls 122 | 123 | echo "Copying new build files" 124 | 125 | find ../build/app/outputs/flutter-apk -type f \( -name '*.apk' -o -name '*.aab' \) -exec cp -v {} . \; 126 | find ../build/app/outputs/bundle -type f \( -name '*.apk' -o -name '*.aab' \) -exec cp -v {} . \; 127 | 128 | ls 129 | 130 | echo "Renaming new build files" 131 | 132 | for file in app*; do 133 | mv $file magic-epaper-$branch-${file#*-} 134 | done 135 | 136 | ls 137 | 138 | echo "Pushing to apk branch" 139 | 140 | git checkout --orphan temporary 141 | git add --all . 142 | git commit -am "[Auto] Update APK/AAB's from $branch ($(date +%Y-%m-%d.%H:%M:%S))" 143 | git branch -D apk 144 | git branch -m apk 145 | git push --force origin apk 146 | 147 | - name: Push app in open testing track 148 | if: ${{ github.repository == 'fossasia/magic-epaper-app' }} 149 | run: | 150 | cd ./android 151 | git clone --branch=fastlane-android --depth=1 https://${{ github.repository_owner }}:${{ github.token }}@github.com/${{ github.repository }} fastlane 152 | fastlane uploadToOpenTesting 153 | if [[ $? -ne 0 ]]; then 154 | exit 1 155 | fi 156 | 157 | ios: 158 | name: iOS Flutter Build 159 | needs: common 160 | runs-on: macos-latest 161 | steps: 162 | - uses: actions/checkout@v4 163 | 164 | - name: Prepare Build Keys 165 | if: ${{ github.repository == 'fossasia/magic-epaper-app' }} 166 | env: 167 | ENCRYPTED_IOS_IV: ${{ secrets.ENCRYPTED_IOS_IV }} 168 | ENCRYPTED_IOS_KEY: ${{ secrets.ENCRYPTED_IOS_KEY }} 169 | run: | 170 | bash scripts/prep-ios-key.sh 171 | 172 | - name: Setup Certs 173 | if: ${{ github.repository == 'fossasia/magic-epaper-app' }} 174 | env: 175 | MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} 176 | MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }} 177 | run: | 178 | cd ./iOS 179 | git clone --branch=fastlane-ios --depth=1 https://${{ github.repository_owner }}:${{ github.token }}@github.com/${{ github.repository }} fastlane 180 | fastlane setupCertificates 181 | if [[ $? -ne 0 ]]; then 182 | exit 1 183 | fi 184 | 185 | - name: iOS Workflow 186 | uses: ./.github/actions/ios 187 | with: 188 | VERSION_NAME: ${{needs.common.outputs.VERSION_NAME}} 189 | VERSION_CODE: ${{needs.common.outputs.VERSION_CODE}} 190 | 191 | - name: Push app to testflight 192 | if: ${{ github.repository == 'fossasia/magic-epaper-app' }} 193 | run: | 194 | cd ./iOS 195 | fastlane uploadToBeta 196 | if [[ $? -ne 0 ]]; then 197 | exit 1 198 | fi 199 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Symbolication related 35 | app.*.symbols 36 | 37 | # Obfuscation related 38 | app.*.map.json 39 | 40 | # Android Studio will place build artifacts here 41 | /android/app/debug 42 | /android/app/profile 43 | /android/app/release 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # magic_epaper_app 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application that follows the 8 | [simple app state management 9 | tutorial](https://flutter.dev/to/state-management-sample). 10 | 11 | For help getting started with Flutter development, view the 12 | [online documentation](https://docs.flutter.dev), which offers tutorials, 13 | samples, guidance on mobile development, and a full API reference. 14 | 15 | ## Assets 16 | 17 | The `assets` directory houses images, fonts, and any other files you want to 18 | include with your application. 19 | 20 | The `assets/images` directory contains [resolution-aware 21 | images](https://flutter.dev/to/resolution-aware-images). 22 | 23 | ## Localization 24 | 25 | This project generates localized messages based on arb files found in 26 | the `lib/src/localization` directory. 27 | 28 | To support additional languages, please visit the tutorial on 29 | [Internationalizing Flutter apps](https://flutter.dev/to/internationalization). 30 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/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 = "org.fossasia.magic_epaper_app" 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 = "org.fossasia.magic_epaper_app" 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 = 26 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 | 29 | 31 | 34 | 35 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/org/fossasia/magic_epaper_app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package org.fossasia.magic_epaper_app 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.provider.Settings 6 | import io.flutter.embedding.android.FlutterActivity 7 | import io.flutter.embedding.engine.FlutterEngine 8 | import io.flutter.plugin.common.MethodChannel 9 | 10 | class MainActivity : FlutterActivity() { 11 | private val CHANNEL = "com.yourapp.nfc/settings" 12 | 13 | override fun configureFlutterEngine(flutterEngine: FlutterEngine) { 14 | super.configureFlutterEngine(flutterEngine) 15 | MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { 16 | call, result -> 17 | if (call.method == "openNFCSettings") { 18 | val intent = Intent(Settings.ACTION_NFC_SETTINGS) 19 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 20 | startActivity(intent) 21 | result.success(null) 22 | } else { 23 | result.notImplemented() 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | 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/build/.last_build_id: -------------------------------------------------------------------------------- 1 | d30b57d976b0a72bbdf40995ef50376a -------------------------------------------------------------------------------- /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/canvas/black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/assets/canvas/black.png -------------------------------------------------------------------------------- /assets/canvas/red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/assets/canvas/red.png -------------------------------------------------------------------------------- /assets/canvas/white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/assets/canvas/white.png -------------------------------------------------------------------------------- /assets/images/2.0x/flutter_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/assets/images/2.0x/flutter_logo.png -------------------------------------------------------------------------------- /assets/images/3.0x/flutter_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/assets/images/3.0x/flutter_logo.png -------------------------------------------------------------------------------- /assets/images/black-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/assets/images/black-red.png -------------------------------------------------------------------------------- /assets/images/displays/epaper_3.7_bw.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/assets/images/displays/epaper_3.7_bw.PNG -------------------------------------------------------------------------------- /assets/images/displays/epaper_3.7_bwr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/assets/images/displays/epaper_3.7_bwr.png -------------------------------------------------------------------------------- /assets/images/h-flip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/assets/images/h-flip.png -------------------------------------------------------------------------------- /assets/images/tux-fit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/assets/images/tux-fit.png -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '13.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 | 33 | flutter_install_all_ios_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_ios_build_settings(target) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /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.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import 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/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/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/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/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/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/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/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/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/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/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/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/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/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/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/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/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/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/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/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/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/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/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/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/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/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/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 | Magic Epaper App 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | magic_epaper_app 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 | -------------------------------------------------------------------------------- /l10n.yaml: -------------------------------------------------------------------------------- 1 | arb-dir: lib/src/localization 2 | template-arb-file: app_en.arb 3 | output-localization-file: app_localizations.dart 4 | -------------------------------------------------------------------------------- /lib/constants/asset_paths.dart: -------------------------------------------------------------------------------- 1 | class ImageAssets { 2 | static const String flipHorizontal = 'assets/images/h-flip.png'; 3 | static const String whiteBoard = 'assets/canvas/white.png'; 4 | static const String redBoard = 'assets/canvas/red.png'; 5 | static const String blackBoard = 'assets/canvas/black.png'; 6 | static const String epaper37Bwr = 'assets/images/displays/epaper_3.7_bwr.png'; 7 | static const String epaper37Bw = 'assets/images/displays/epaper_3.7_bw.PNG'; 8 | } 9 | -------------------------------------------------------------------------------- /lib/constants/color_constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// Primary Colors 4 | const Color colorPrimary = Color(0xFFD32F2F); 5 | const Color colorPrimaryDark = Color(0xFFC72C2C); 6 | const Color colorAccent = Color(0xFFD32F2F); 7 | 8 | /// Knob Colors 9 | const Color backCircleColor = Color(0xFFEDEDED); 10 | const Color indicatorColor = Color(0xFFD32F2F); 11 | const Color progressSecondaryColor = Color(0xFFEEEEEE); 12 | 13 | /// Additional Colors 14 | const Color mdGrey400 = Color(0xFFBDBDBD); 15 | const Color dividerColor = Color(0xFFE0E0E0); 16 | const Color drawerHeaderTitle = Color(0xFFFFFFFF); 17 | -------------------------------------------------------------------------------- /lib/constants/string_constants.dart: -------------------------------------------------------------------------------- 1 | class StringConstants { 2 | static const String appName = 'Magic epaper'; 3 | } 4 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:magic_epaper_app/provider/getitlocator.dart'; 3 | import 'package:magic_epaper_app/provider/image_loader.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:magic_epaper_app/view/display_selection_screen.dart'; 6 | 7 | void main() { 8 | setupLocator(); 9 | runApp(MultiProvider(providers: [ 10 | ChangeNotifierProvider(create: (context) => ImageLoader()), 11 | ], child: const MyApp())); 12 | } 13 | 14 | class MyApp extends StatelessWidget { 15 | const MyApp({super.key}); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return const MaterialApp( 20 | title: 'Magic Epaper', 21 | home: DisplaySelectionScreen(), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/pro_image_editor/core/constants/example_constants.dart: -------------------------------------------------------------------------------- 1 | /// A path to a demo image asset in the project. 2 | final String kImageEditorExampleAssetPath = 'assets/demo.png'; 3 | 4 | /// A URL to a demo image hosted on a remote server. 5 | final String kImageEditorExampleNetworkUrl = 6 | 'https://picsum.photos/id/230/2000'; 7 | 8 | /// A URL to a demo image hosted on a remote server. 9 | final String kVideoEditorExampleAssetPath = 'assets/demo.mp4'; 10 | 11 | /// Breakpoint for desktop layout in the image editor example. 12 | final kImageEditorExampleIsDesktopBreakPoint = 900; 13 | -------------------------------------------------------------------------------- /lib/pro_image_editor/core/mixin/example_helper.dart: -------------------------------------------------------------------------------- 1 | // Dart imports: 2 | import 'dart:io'; 3 | import 'dart:ui' as ui; 4 | 5 | import 'package:flutter/foundation.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:pro_image_editor/pro_image_editor.dart'; 8 | import 'package:vibration/vibration.dart'; 9 | 10 | import '../../features/preview/preview_img.dart'; 11 | import '../constants/example_constants.dart'; 12 | export '../../shared/widgets/prepare_image_widget.dart'; 13 | 14 | /// A mixin that provides helper methods and state management for image editing 15 | /// using the [ProImageEditor]. It is intended to be used in a [StatefulWidget]. 16 | mixin ExampleHelperState on State { 17 | /// The global key used to reference the state of [ProImageEditor]. 18 | final editorKey = GlobalKey(); 19 | 20 | /// Holds the edited image bytes after the editing is complete. 21 | Uint8List? editedBytes; 22 | 23 | /// The time it took to generate the edited image in milliseconds. 24 | double? _generationTime; 25 | 26 | /// Records the start time of the editing process. 27 | DateTime? startEditingTime; 28 | 29 | /// Indicates whether image-resources are pre-cached. 30 | bool isPreCached = true; 31 | 32 | bool _deviceCanVibrate = false; 33 | bool _deviceCanCustomVibrate = false; 34 | 35 | @override 36 | void initState() { 37 | super.initState(); 38 | 39 | Vibration.hasVibrator().then((hasVibrator) async { 40 | _deviceCanVibrate = hasVibrator; 41 | 42 | if (!hasVibrator || !mounted) return; 43 | 44 | _deviceCanCustomVibrate = await Vibration.hasCustomVibrationsSupport(); 45 | }); 46 | } 47 | 48 | /// Determines if the current layout should use desktop mode based on the 49 | /// screen width. 50 | /// 51 | /// Returns `true` if the screen width is greater than or equal to 52 | /// [kImageEditorExampleIsDesktopBreakPoint], otherwise `false`. 53 | bool isDesktopMode(BuildContext context) => 54 | MediaQuery.sizeOf(context).width >= 55 | kImageEditorExampleIsDesktopBreakPoint; 56 | 57 | /// Called when the image editing process starts. 58 | /// Records the time when editing began. 59 | Future onImageEditingStarted() async { 60 | startEditingTime = DateTime.now(); 61 | } 62 | 63 | /// Called when the image editing process is complete. 64 | /// Saves the edited image bytes and calculates the generation time. 65 | /// 66 | /// [bytes] is the edited image in bytes. 67 | Future onImageEditingComplete(Uint8List bytes) async { 68 | editedBytes = bytes; 69 | setGenerationTime(); 70 | } 71 | 72 | /// Calculates the time taken for the image generation in milliseconds 73 | /// and stores it in [_generationTime]. 74 | void setGenerationTime() { 75 | if (startEditingTime != null) { 76 | _generationTime = DateTime.now() 77 | .difference(startEditingTime!) 78 | .inMilliseconds 79 | .toDouble(); 80 | } 81 | } 82 | 83 | /// Closes the image editor and navigates to a preview page showing the 84 | /// edited image. 85 | /// 86 | /// If [showThumbnail] is true, a thumbnail of the image will be displayed. 87 | /// The [rawOriginalImage] can be passed if the unedited image needs to be 88 | /// shown. 89 | /// The [generationConfigs] can be used to pass additional configurations for 90 | /// generating the image. 91 | void onCloseEditor({ 92 | required EditorMode editorMode, 93 | bool enablePop = true, 94 | bool showThumbnail = false, 95 | ui.Image? rawOriginalImage, 96 | final ImageGenerationConfigs? generationConfigs, 97 | }) async { 98 | if (editorMode != EditorMode.main) return Navigator.pop(context); 99 | 100 | if (editedBytes != null) { 101 | // Pre-cache the edited image to improve display performance. 102 | await precacheImage(MemoryImage(editedBytes!), context); 103 | if (!mounted) return; 104 | 105 | // Navigate to the preview page to display the edited image. 106 | editorKey.currentState?.isPopScopeDisabled = true; 107 | await Navigator.push( 108 | context, 109 | MaterialPageRoute( 110 | builder: (context) { 111 | return PreviewImgPage( 112 | imgBytes: editedBytes!, 113 | generationTime: _generationTime, 114 | showThumbnail: showThumbnail, 115 | rawOriginalImage: rawOriginalImage, 116 | generationConfigs: generationConfigs, 117 | ); 118 | }, 119 | ), 120 | ).whenComplete(() { 121 | // Reset the state variables after navigation. 122 | editedBytes = null; 123 | _generationTime = null; 124 | startEditingTime = null; 125 | }); 126 | } 127 | 128 | if (mounted && enablePop) { 129 | Navigator.pop(context); 130 | } 131 | } 132 | 133 | /// Preloads an image into memory to improve performance. 134 | /// 135 | /// Supports both asset and network images. Once the image is cached, it 136 | /// updates the 137 | /// [isPreCached] flag, triggers a widget rebuild, and optionally executes a 138 | /// callback. 139 | /// 140 | /// Parameters: 141 | /// - [assetPath]: The file path of the asset image to be cached. 142 | /// - [networkUrl]: The URL of the network image to be cached. 143 | /// - [onDone]: An optional callback executed after caching is complete. 144 | /// 145 | /// Ensures the widget is still mounted before performing operations. 146 | void preCacheImage({ 147 | String? assetPath, 148 | String? networkUrl, 149 | Function()? onDone, 150 | }) { 151 | isPreCached = false; 152 | WidgetsBinding.instance.addPostFrameCallback((_) { 153 | if (!mounted) return; 154 | precacheImage( 155 | assetPath != null 156 | ? AssetImage(assetPath) 157 | : NetworkImage(networkUrl!) as ImageProvider, 158 | context) 159 | .whenComplete(() { 160 | if (!mounted) return; 161 | isPreCached = true; 162 | setState(() {}); 163 | onDone?.call(); 164 | }); 165 | }); 166 | } 167 | 168 | /// Vibrates the device briefly if enabled and supported. 169 | /// 170 | /// If the device supports custom vibrations, it uses the `Vibration.vibrate` 171 | /// method with a duration of 3 milliseconds to produce the vibration. 172 | /// 173 | /// On older Android devices, it initiates vibration using 174 | /// `Vibration.vibrate`, and then, after 3 milliseconds, cancels the 175 | /// vibration using `Vibration.cancel`. 176 | /// 177 | /// This function is used to provide haptic feedback when helper lines are 178 | /// interacted with, enhancing the user experience. 179 | void vibrateLineHit() { 180 | if (_deviceCanVibrate && _deviceCanCustomVibrate) { 181 | Vibration.vibrate(duration: 3); 182 | } else if (!kIsWeb && Platform.isAndroid) { 183 | /// On old android devices we can stop the vibration after 3 milliseconds 184 | /// iOS: only works for custom haptic vibrations using CHHapticEngine. 185 | /// This will set `deviceCanCustomVibrate` anyway to true so it's 186 | /// impossible to fake it. 187 | Vibration.vibrate(); 188 | Future.delayed(const Duration(milliseconds: 3)) 189 | .whenComplete(Vibration.cancel); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /lib/pro_image_editor/core/models/example_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | /// Represents an example item with a name, path, icon, and associated page. 4 | /// 5 | /// This class is typically used to define examples in an application, where 6 | /// each example has: 7 | /// - A [path]: The navigation route or identifier for the example. 8 | /// - A [name]: A user-friendly name for the example. 9 | /// - An [icon]: An icon representing the example. 10 | /// - A [page]: The widget representing the content of the example. 11 | class Example { 12 | /// Creates an instance of [Example]. 13 | /// 14 | /// All fields are required. 15 | const Example({ 16 | required this.path, 17 | required this.name, 18 | required this.icon, 19 | required this.page, 20 | }); 21 | 22 | /// The navigation route or identifier for the example. 23 | final String path; 24 | 25 | /// The user-friendly name of the example. 26 | final String name; 27 | 28 | /// The icon representing the example. 29 | final IconData icon; 30 | 31 | /// The widget representing the content of the example. 32 | final Widget page; 33 | } 34 | -------------------------------------------------------------------------------- /lib/pro_image_editor/features/bottom_bar.dart: -------------------------------------------------------------------------------- 1 | // Flutter imports: 2 | import 'package:flutter/material.dart'; 3 | import 'package:magic_epaper_app/pro_image_editor/features/color_picker.dart'; 4 | import 'package:pro_image_editor/core/models/editor_configs/pro_image_editor_configs.dart'; 5 | import 'package:pro_image_editor/core/ui/pro_image_editor_icons.dart'; 6 | 7 | /// Represents the bottom bar for the paint functionality in the WhatsApp theme. 8 | /// 9 | /// This widget provides controls for adjusting the stroke width and color 10 | /// for paint operations, using a design inspired by WhatsApp. 11 | class BottomBarCustom extends StatefulWidget { 12 | /// Creates a [BottomBarCustom] widget. 13 | /// 14 | /// This bottom bar allows users to select stroke widths and colors for 15 | /// paint, integrating seamlessly with the WhatsApp theme. 16 | /// 17 | /// Example: 18 | /// ``` 19 | /// BottomBarCustom( 20 | /// configs: myEditorConfigs, 21 | /// strokeWidth: 5.0, 22 | /// onSetLineWidth: (width) { 23 | /// // Handle stroke width change 24 | /// }, 25 | /// initColor: Colors.black, 26 | /// onColorChanged: (color) { 27 | /// // Handle color change 28 | /// }, 29 | /// ) 30 | /// ``` 31 | const BottomBarCustom({ 32 | super.key, 33 | required this.configs, 34 | required this.strokeWidth, 35 | required this.onSetLineWidth, 36 | required this.onColorChanged, 37 | this.iconStrokeWidthThin = ProImageEditorIcons.penSize1, 38 | this.iconStrokeWidthMedium = ProImageEditorIcons.penSize2, 39 | this.iconStrokeWidthBold = ProImageEditorIcons.penSize3, 40 | }); 41 | 42 | /// The configuration for the image editor. 43 | /// 44 | /// These settings determine various aspects of the bottom bar's behavior 45 | /// and appearance, ensuring it aligns with the application's overall theme. 46 | final ProImageEditorConfigs configs; 47 | 48 | /// The current stroke width for paint. 49 | /// 50 | /// This value determines the thickness of the lines drawn in the paint 51 | /// editor, allowing users to customize the appearance of their artwork. 52 | final double strokeWidth; 53 | 54 | /// Callback function for setting the stroke width. 55 | /// 56 | /// This function is called whenever the user selects a different stroke 57 | /// width, allowing the application to update the line thickness. 58 | final Function(double value) onSetLineWidth; 59 | 60 | /// The initial color for paint. 61 | /// 62 | /// This color sets the initial paint color, providing a starting point 63 | /// for color customization. 64 | final Color initColor = Colors.black; 65 | 66 | /// Callback function for handling color changes. 67 | /// 68 | /// This function is called whenever the user selects a new color, allowing 69 | /// the application to update the paint color. 70 | final ValueChanged onColorChanged; 71 | 72 | /// Icon representing thin stroke width. 73 | /// 74 | /// This icon is used to visually represent the option for selecting a 75 | /// thin stroke width in the paint toolbar. 76 | final IconData iconStrokeWidthThin; 77 | 78 | /// Icon representing medium stroke width. 79 | /// 80 | /// This icon is used to visually represent the option for selecting a 81 | /// medium stroke width in the paint toolbar. 82 | final IconData iconStrokeWidthMedium; 83 | 84 | /// Icon representing bold stroke width. 85 | /// 86 | /// This icon is used to visually represent the option for selecting a 87 | /// bold stroke width in the paint toolbar. 88 | final IconData iconStrokeWidthBold; 89 | 90 | @override 91 | State createState() => _BottomBarCustomState(); 92 | } 93 | 94 | class _BottomBarCustomState extends State { 95 | final double _space = 10; 96 | 97 | bool _showColorPicker = true; 98 | 99 | bool get _isMaterial => 100 | widget.configs.designMode == ImageEditorDesignMode.material; 101 | 102 | @override 103 | Widget build(BuildContext context) { 104 | return Positioned( 105 | bottom: _space, 106 | left: _space, 107 | right: 0, 108 | height: 40, 109 | child: !_isMaterial 110 | ? _buildLineWidths() 111 | : Row( 112 | crossAxisAlignment: CrossAxisAlignment.stretch, 113 | mainAxisAlignment: MainAxisAlignment.start, 114 | children: [ 115 | IconButton( 116 | onPressed: () { 117 | setState(() { 118 | _showColorPicker = !_showColorPicker; 119 | }); 120 | }, 121 | icon: Icon( 122 | _showColorPicker ? Icons.draw : Icons.color_lens, 123 | ), 124 | style: IconButton.styleFrom(backgroundColor: Colors.black38), 125 | ), 126 | Container( 127 | margin: const EdgeInsets.fromLTRB(14.0, 4, 0, 4), 128 | width: 1.5, 129 | decoration: BoxDecoration( 130 | color: Colors.white54, 131 | borderRadius: BorderRadius.circular(2), 132 | ), 133 | ), 134 | _showColorPicker 135 | ? Expanded( 136 | child: ColorPickerCustom( 137 | onColorChanged: widget.onColorChanged, 138 | ), 139 | ) 140 | : _buildLineWidths(), 141 | ], 142 | ), 143 | ); 144 | } 145 | 146 | Widget _buildLineWidths() { 147 | ButtonStyle buttonStyle = IconButton.styleFrom( 148 | backgroundColor: Colors.black38, 149 | foregroundColor: 150 | widget.configs.paintEditor.style.bottomBarInactiveItemColor, 151 | padding: const EdgeInsets.all(10), 152 | iconSize: 22, 153 | minimumSize: const Size.fromRadius(10), 154 | ); 155 | return Padding( 156 | padding: const EdgeInsets.only(left: 14), 157 | child: Wrap( 158 | alignment: 159 | _isMaterial ? WrapAlignment.start : WrapAlignment.spaceEvenly, 160 | runAlignment: WrapAlignment.center, 161 | spacing: 10, 162 | children: [ 163 | IconButton( 164 | onPressed: () { 165 | widget.onSetLineWidth(2); 166 | }, 167 | icon: Icon(widget.iconStrokeWidthThin), 168 | style: buttonStyle.copyWith( 169 | backgroundColor: widget.strokeWidth != 2 170 | ? null 171 | : const WidgetStatePropertyAll(Colors.white), 172 | foregroundColor: widget.strokeWidth != 2 173 | ? null 174 | : const WidgetStatePropertyAll(Colors.black), 175 | ), 176 | ), 177 | IconButton( 178 | onPressed: () { 179 | widget.onSetLineWidth(5); 180 | }, 181 | icon: Icon(widget.iconStrokeWidthMedium), 182 | style: buttonStyle.copyWith( 183 | backgroundColor: widget.strokeWidth != 5 184 | ? null 185 | : const WidgetStatePropertyAll(Colors.white), 186 | foregroundColor: widget.strokeWidth != 5 187 | ? null 188 | : const WidgetStatePropertyAll(Colors.black), 189 | ), 190 | ), 191 | IconButton( 192 | onPressed: () { 193 | widget.onSetLineWidth(10); 194 | }, 195 | icon: Icon(widget.iconStrokeWidthBold), 196 | style: buttonStyle.copyWith( 197 | backgroundColor: widget.strokeWidth != 10 198 | ? null 199 | : const WidgetStatePropertyAll(Colors.white), 200 | foregroundColor: widget.strokeWidth != 10 201 | ? null 202 | : const WidgetStatePropertyAll(Colors.black), 203 | ), 204 | ), 205 | ], 206 | ), 207 | ); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /lib/pro_image_editor/features/color_picker.dart: -------------------------------------------------------------------------------- 1 | // Flutter imports: 2 | import 'package:flutter/material.dart'; 3 | import 'package:magic_epaper_app/provider/color_palette_provider.dart'; 4 | import 'package:magic_epaper_app/provider/getitlocator.dart'; 5 | 6 | /// A stateful widget that provides a color picker inspired by WhatsApp. 7 | /// 8 | /// This color picker allows users to select a color, providing a callback for 9 | /// color changes and initializing with a specified color. 10 | class ColorPickerCustom extends StatefulWidget { 11 | /// Creates a [ColorPickerCustom]. 12 | /// 13 | /// This color picker lets users select a color, triggering a callback when 14 | /// the color changes, and initializing with a specified color. 15 | /// 16 | /// Example: 17 | /// ``` 18 | /// ColorPickerCustom( 19 | /// onColorChanged: (color) { 20 | /// // Handle color change 21 | /// }, 22 | /// initColor: Colors.blue, 23 | /// ) 24 | /// ``` 25 | const ColorPickerCustom({ 26 | super.key, 27 | required this.onColorChanged, 28 | }); 29 | 30 | /// Callback for handling color changes. 31 | /// 32 | /// This callback is triggered whenever the user selects a new color, allowing 33 | /// the application to update its UI or perform other actions. 34 | final ValueChanged onColorChanged; 35 | 36 | /// The initial color selected in the color picker. 37 | /// 38 | /// This color sets the initial value of the picker, providing a starting 39 | /// point for color selection. 40 | 41 | @override 42 | State createState() => _ColorPickerCustomState(); 43 | } 44 | 45 | class _ColorPickerCustomState extends State { 46 | Color _selectedColor = Colors.black; 47 | final List _colors = getIt().colors; 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return ListView.builder( 52 | padding: const EdgeInsets.symmetric(horizontal: 14), 53 | scrollDirection: Axis.horizontal, 54 | primary: false, 55 | shrinkWrap: true, 56 | itemBuilder: (context, index) { 57 | Color color = _colors[index]; 58 | bool selected = _selectedColor == color; 59 | double size = !selected ? 20 : 24; 60 | double borderWidth = !selected ? 2.5 : 4; 61 | return Center( 62 | child: GestureDetector( 63 | onTap: () { 64 | setState(() { 65 | _selectedColor = color; 66 | widget.onColorChanged(color); 67 | }); 68 | }, 69 | child: AnimatedContainer( 70 | margin: const EdgeInsets.symmetric(horizontal: 10.0), 71 | duration: const Duration(milliseconds: 100), 72 | width: size, 73 | height: size, 74 | decoration: BoxDecoration( 75 | color: color, 76 | borderRadius: BorderRadius.circular(100), 77 | border: Border.all( 78 | color: Colors.grey, 79 | width: borderWidth, 80 | ), 81 | ), 82 | ), 83 | ), 84 | ); 85 | }, 86 | itemCount: _colors.length, 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/pro_image_editor/features/text_bottom_bar.dart: -------------------------------------------------------------------------------- 1 | // Flutter imports: 2 | import 'package:flutter/material.dart'; 3 | import 'package:magic_epaper_app/pro_image_editor/features/color_picker.dart'; 4 | 5 | // Project imports: 6 | import 'package:pro_image_editor/features/text_editor/widgets/text_editor_bottom_bar.dart'; 7 | import 'package:pro_image_editor/pro_image_editor.dart'; 8 | 9 | /// A stateful widget that represents the bottom bar for text editing in the 10 | /// WhatsApp theme. 11 | /// 12 | /// This widget provides controls for adjusting text color and font style, 13 | /// using a design inspired by WhatsApp. 14 | 15 | class TextBottomBar extends StatefulWidget { 16 | /// Creates a [TextBottomBar] widget. 17 | /// 18 | /// This bottom bar allows users to customize text color and font style, 19 | /// integrating seamlessly with the WhatsApp theme. 20 | /// 21 | /// Example: 22 | /// ``` 23 | /// TextBottomBar( 24 | /// configs: myEditorConfigs, 25 | /// initColor: Colors.black, 26 | /// onColorChanged: (color) { 27 | /// // Handle color change 28 | /// }, 29 | /// selectedStyle: TextStyle(fontSize: 16), 30 | /// onFontChange: (style) { 31 | /// // Handle font change 32 | /// }, 33 | /// ) 34 | /// ``` 35 | const TextBottomBar({ 36 | super.key, 37 | required this.configs, 38 | required this.initColor, 39 | required this.onColorChanged, 40 | required this.selectedStyle, 41 | required this.onFontChange, 42 | }); 43 | 44 | /// The configuration for the image editor. 45 | /// 46 | /// These settings determine various aspects of the bottom bar's behavior and 47 | /// appearance, ensuring it aligns with the application's overall theme. 48 | final ProImageEditorConfigs configs; 49 | 50 | /// The initial color for the text. 51 | /// 52 | /// This color sets the initial text color, providing a starting point for 53 | /// color customization. 54 | final Color initColor; 55 | 56 | /// Callback function for handling color changes. 57 | /// 58 | /// This function is called whenever the user selects a new text color, 59 | /// allowing the application to update the text appearance. 60 | final ValueChanged onColorChanged; 61 | 62 | /// The currently selected text style. 63 | /// 64 | /// This style is used to initialize the font selection, providing a starting 65 | /// point for font customization and styling. 66 | final TextStyle selectedStyle; 67 | 68 | /// Callback function for changing the text font style. 69 | /// 70 | /// This function is called whenever the user selects a new font style, 71 | /// allowing the application to update the text style. 72 | final Function(TextStyle style) onFontChange; 73 | 74 | @override 75 | State createState() => _TextBottomBarState(); 76 | } 77 | 78 | class _TextBottomBarState extends State { 79 | final double _space = 10; 80 | 81 | bool _showColorPicker = true; 82 | 83 | @override 84 | Widget build(BuildContext context) { 85 | return Positioned( 86 | bottom: _space + MediaQuery.viewPaddingOf(context).bottom, 87 | left: _space, 88 | right: 0, 89 | height: 40, 90 | child: widget.configs.designMode == ImageEditorDesignMode.cupertino 91 | ? TextEditorBottomBar( 92 | configs: widget.configs, 93 | selectedStyle: widget.selectedStyle, 94 | onFontChange: widget.onFontChange, 95 | ) 96 | : Row( 97 | crossAxisAlignment: CrossAxisAlignment.stretch, 98 | mainAxisAlignment: MainAxisAlignment.start, 99 | children: [ 100 | IconButton( 101 | onPressed: () { 102 | setState(() { 103 | _showColorPicker = !_showColorPicker; 104 | }); 105 | }, 106 | icon: !_showColorPicker 107 | ? const Icon(Icons.color_lens) 108 | : Text( 109 | 'Aa', 110 | style: 111 | widget.configs.textEditor.customTextStyles?.first, 112 | ), 113 | style: IconButton.styleFrom(backgroundColor: Colors.black38), 114 | ), 115 | Container( 116 | margin: const EdgeInsets.fromLTRB(14.0, 4, 0, 4), 117 | width: 1.5, 118 | decoration: BoxDecoration( 119 | color: Colors.white54, 120 | borderRadius: BorderRadius.circular(2), 121 | ), 122 | ), 123 | _showColorPicker 124 | ? Expanded( 125 | child: ColorPickerCustom( 126 | onColorChanged: widget.onColorChanged, 127 | ), 128 | ) 129 | : Expanded( 130 | child: TextEditorBottomBar( 131 | configs: widget.configs, 132 | selectedStyle: widget.selectedStyle, 133 | onFontChange: widget.onFontChange, 134 | ), 135 | ), 136 | ], 137 | ), 138 | ); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /lib/pro_image_editor/shared/widgets/demo_build_stickers.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:pro_image_editor/pro_image_editor.dart'; 5 | 6 | /// A widget that demonstrates the building of sticker categories and displays 7 | /// them in a scrollable grid layout. It also allows interaction with stickers 8 | /// to be set in an image editor layer. 9 | class DemoBuildStickers extends StatelessWidget { 10 | /// Creates a [DemoBuildStickers] widget. 11 | /// 12 | /// [setLayer] is a callback function to set the selected sticker widget in 13 | /// the editor. 14 | /// [scrollController] controls the scroll behavior of the sticker list. 15 | /// [categoryColor] defines the background color of the category bar. 16 | DemoBuildStickers({ 17 | super.key, 18 | required this.setLayer, 19 | required this.scrollController, 20 | this.categoryColor = const Color(0xFF424242), 21 | }); 22 | 23 | /// Callback function to set the selected sticker in the image editor layer. 24 | final Function(Widget) setLayer; 25 | 26 | /// Controls the scroll behavior of the sticker grid. 27 | final ScrollController scrollController; 28 | 29 | /// Color for the category selection bar. 30 | final Color categoryColor; 31 | 32 | /// Titles for the sticker categories. 33 | final List demoTitles = [ 34 | 'Recent', 35 | 'Favorites', 36 | 'Shapes', 37 | 'Funny', 38 | 'Boring', 39 | 'Frog', 40 | 'Snow', 41 | 'More' 42 | ]; 43 | @override 44 | Widget build(BuildContext context) { 45 | List slivers = []; 46 | int offset = 0; 47 | for (var element in demoTitles) { 48 | slivers.addAll([ 49 | SliverPadding( 50 | padding: const EdgeInsets.only(bottom: 4), 51 | sliver: SliverToBoxAdapter( 52 | child: Text( 53 | element, 54 | style: const TextStyle(color: Colors.white), 55 | ), 56 | ), 57 | ), 58 | _buildDemoStickers(offset, setLayer), 59 | const SliverToBoxAdapter(child: SizedBox(height: 20)), 60 | ]); 61 | offset += 20; 62 | } 63 | 64 | return Column( 65 | children: [ 66 | Expanded( 67 | child: Padding( 68 | padding: const EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 0), 69 | child: CustomScrollView( 70 | controller: scrollController, 71 | slivers: slivers, 72 | ), 73 | ), 74 | ), 75 | Container( 76 | height: 50, 77 | color: categoryColor, 78 | child: Row( 79 | children: [ 80 | IconButton( 81 | onPressed: () {}, 82 | icon: const Icon(Icons.watch_later_outlined), 83 | color: Colors.white, 84 | ), 85 | IconButton( 86 | onPressed: () {}, 87 | icon: const Icon(Icons.mood), 88 | color: Colors.white, 89 | ), 90 | IconButton( 91 | onPressed: () {}, 92 | icon: const Icon(Icons.pets), 93 | color: Colors.white, 94 | ), 95 | IconButton( 96 | onPressed: () {}, 97 | icon: const Icon(Icons.coronavirus), 98 | color: Colors.white, 99 | ), 100 | ], 101 | ), 102 | ), 103 | ], 104 | ); 105 | } 106 | 107 | SliverGrid _buildDemoStickers(int offset, Function(Widget) setLayer) { 108 | return SliverGrid.builder( 109 | gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( 110 | maxCrossAxisExtent: 80, 111 | mainAxisSpacing: 10, 112 | crossAxisSpacing: 10, 113 | ), 114 | itemCount: max(3, 3 + offset % 6), 115 | itemBuilder: (context, index) { 116 | String url = 117 | 'https://picsum.photos/id/${offset + (index + 3) * 3}/2000'; 118 | var widget = ClipRRect( 119 | borderRadius: BorderRadius.circular(7), 120 | child: Image.network( 121 | url, 122 | width: 120, 123 | height: 120, 124 | fit: BoxFit.cover, 125 | loadingBuilder: (context, child, loadingProgress) { 126 | return AnimatedSwitcher( 127 | layoutBuilder: (currentChild, previousChildren) { 128 | return SizedBox( 129 | width: 120, 130 | height: 120, 131 | child: Stack( 132 | fit: StackFit.expand, 133 | alignment: Alignment.center, 134 | children: [ 135 | ...previousChildren, 136 | if (currentChild != null) currentChild, 137 | ], 138 | ), 139 | ); 140 | }, 141 | duration: const Duration(milliseconds: 200), 142 | child: loadingProgress == null 143 | ? child 144 | : Center( 145 | child: CircularProgressIndicator( 146 | value: loadingProgress.expectedTotalBytes != null 147 | ? loadingProgress.cumulativeBytesLoaded / 148 | loadingProgress.expectedTotalBytes! 149 | : null, 150 | ), 151 | ), 152 | ); 153 | }, 154 | ), 155 | ); 156 | return GestureDetector( 157 | onTap: () async { 158 | // Important make sure the image is completely loaded 159 | // cuz the editor will directly take a screenshot 160 | // inside of a background isolated thread. 161 | LoadingDialog.instance.show( 162 | context, 163 | configs: const ProImageEditorConfigs(), 164 | theme: Theme.of(context), 165 | ); 166 | 167 | await precacheImage(NetworkImage(url), context); 168 | LoadingDialog.instance.hide(); 169 | setLayer(widget); 170 | }, 171 | child: MouseRegion( 172 | cursor: SystemMouseCursors.click, 173 | child: widget, 174 | ), 175 | ); 176 | }); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /lib/pro_image_editor/shared/widgets/material_icon_button.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | /// A stateless widget that displays a material-styled icon button with a custom 6 | /// circular background, half of which is a secondary color. Below the icon, 7 | /// a label text is displayed. 8 | /// 9 | /// The [MaterialIconActionButton] widget requires a primary color, secondary 10 | /// color, icon, text, and a callback function to handle taps. 11 | /// 12 | /// Example usage: 13 | /// ```dart 14 | /// MaterialIconActionButton( 15 | /// primaryColor: Colors.blue, 16 | /// secondaryColor: Colors.green, 17 | /// icon: Icons.camera, 18 | /// text: 'Camera', 19 | /// onTap: () { 20 | /// // Handle tap action 21 | /// }, 22 | /// ); 23 | /// ``` 24 | class MaterialIconActionButton extends StatelessWidget { 25 | /// Creates a new [MaterialIconActionButton] widget. 26 | /// 27 | /// The [primaryColor] is the color of the circular background, while the 28 | /// [secondaryColor] is used for the half-circle overlay. The [icon] is the 29 | /// icon to display in the center, and [text] is the label displayed below 30 | /// the icon. The [onTap] callback is triggered when the button is tapped. 31 | const MaterialIconActionButton({ 32 | super.key, 33 | required this.primaryColor, 34 | required this.secondaryColor, 35 | required this.icon, 36 | required this.text, 37 | required this.onTap, 38 | }); 39 | 40 | /// The primary color for the button's background. 41 | final Color primaryColor; 42 | 43 | /// The secondary color for the half-circle overlay. 44 | final Color secondaryColor; 45 | 46 | /// The icon to display in the center of the button. 47 | final IconData icon; 48 | 49 | /// The label displayed below the icon. 50 | final String text; 51 | 52 | /// The callback function triggered when the button is tapped. 53 | final VoidCallback onTap; 54 | 55 | @override 56 | Widget build(BuildContext context) { 57 | return SizedBox( 58 | width: 65, 59 | child: Column( 60 | crossAxisAlignment: CrossAxisAlignment.center, 61 | children: [ 62 | InkWell( 63 | borderRadius: BorderRadius.circular(60), 64 | onTap: onTap, 65 | child: Stack( 66 | alignment: Alignment.center, 67 | children: [ 68 | Container( 69 | width: 60, 70 | height: 60, 71 | decoration: BoxDecoration( 72 | color: primaryColor, 73 | borderRadius: BorderRadius.circular(100), 74 | ), 75 | ), 76 | CustomPaint( 77 | painter: CircleHalf(secondaryColor), 78 | size: const Size(60, 60), 79 | ), 80 | Icon(icon, color: Colors.white), 81 | ], 82 | ), 83 | ), 84 | const SizedBox(height: 7), 85 | Text( 86 | text, 87 | overflow: TextOverflow.ellipsis, 88 | maxLines: 1, 89 | textAlign: TextAlign.center, 90 | ), 91 | ], 92 | ), 93 | ); 94 | } 95 | } 96 | 97 | /// A custom painter class that paints a half-circle. 98 | /// 99 | /// The [CircleHalf] class takes a [color] parameter and paints half of a circle 100 | /// on a canvas, typically used as an overlay for the 101 | /// [MaterialIconActionButton]. 102 | class CircleHalf extends CustomPainter { 103 | /// Creates a new [CircleHalf] painter with the given [color]. 104 | CircleHalf(this.color); 105 | 106 | /// The color to use for paint the half-circle. 107 | final Color color; 108 | 109 | @override 110 | void paint(Canvas canvas, Size size) { 111 | Paint paint = Paint()..color = color; 112 | canvas.drawArc( 113 | Rect.fromCenter( 114 | center: Offset(size.height / 2, size.width / 2), 115 | height: size.height, 116 | width: size.width, 117 | ), 118 | pi, 119 | pi, 120 | false, 121 | paint, 122 | ); 123 | } 124 | 125 | @override 126 | bool shouldRepaint(CustomPainter oldDelegate) => false; 127 | } 128 | -------------------------------------------------------------------------------- /lib/pro_image_editor/shared/widgets/not_found_example.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// A widget that displays a centered "Example not found" message. 4 | /// 5 | /// This widget is useful as a placeholder for screens or routes 6 | /// that have not been implemented yet or when content is unavailable. 7 | class NotFoundExample extends StatefulWidget { 8 | /// Creates a `NotFoundExample` widget. 9 | const NotFoundExample({super.key}); 10 | 11 | @override 12 | State createState() => _NotFoundExampleState(); 13 | } 14 | 15 | class _NotFoundExampleState extends State { 16 | @override 17 | Widget build(BuildContext context) { 18 | return const Scaffold( 19 | body: Center( 20 | child: Text( 21 | 'Example not found', 22 | style: TextStyle( 23 | fontSize: 20, 24 | color: Colors.red, 25 | ), 26 | ), 27 | ), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/pro_image_editor/shared/widgets/paragraph_info_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | /// A widget that visually highlights a paragraph with a colored left border. 4 | /// 5 | /// Useful for drawing attention to a block of content, such as a tip, 6 | /// warning, or important note. 7 | /// 8 | /// You can customize the [color] of the border and apply optional [margin] 9 | /// around the widget. The [child] is the content displayed inside. 10 | class ParagraphInfoWidget extends StatelessWidget { 11 | /// Creates a [ParagraphInfoWidget]. 12 | /// 13 | /// The [child] is required and will be displayed inside the bordered area. 14 | /// You can optionally customize the [margin] and [color]. 15 | const ParagraphInfoWidget({ 16 | super.key, 17 | required this.child, 18 | this.margin, 19 | this.color = const Color(0xFF0f7dff), 20 | }); 21 | 22 | /// The widget displayed inside the bordered container. 23 | final Widget child; 24 | 25 | /// Optional margin around the container. 26 | final EdgeInsets? margin; 27 | 28 | /// The color of the left border. 29 | final Color color; 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return Container( 34 | margin: margin, 35 | padding: const EdgeInsets.only(left: 16), 36 | decoration: BoxDecoration( 37 | border: Border( 38 | left: BorderSide( 39 | color: color, 40 | width: 2, 41 | ), 42 | ), 43 | ), 44 | child: child, 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/pro_image_editor/shared/widgets/pixel_transparent_painter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | /// A custom painter that creates a pixelated transparent pattern. 4 | /// 5 | /// The [PixelTransparentPainter] widget is a [CustomPainter] that paints a 6 | /// checkered grid pattern using two alternating colors. This is often used 7 | /// to represent transparency in image editing applications. 8 | /// 9 | /// The grid is made up of square cells, with the size of each cell controlled 10 | /// by the [cellSize] constant. 11 | /// 12 | /// Example usage: 13 | /// ```dart 14 | /// PixelTransparentPainter( 15 | /// primary: Colors.white, 16 | /// secondary: Colors.grey, 17 | /// ); 18 | /// ``` 19 | class PixelTransparentPainter extends CustomPainter { 20 | /// Creates a new [PixelTransparentPainter] with the given colors. 21 | /// 22 | /// The [primary] and [secondary] colors are used to alternate between the 23 | /// cells in the grid. 24 | const PixelTransparentPainter({ 25 | required this.primary, 26 | required this.secondary, 27 | }); 28 | 29 | /// The primary color used for alternating cells in the grid. 30 | final Color primary; 31 | 32 | /// The secondary color used for alternating cells in the grid. 33 | final Color secondary; 34 | 35 | @override 36 | void paint(Canvas canvas, Size size) { 37 | const cellSize = 22.0; // Size of each square 38 | final numCellsX = size.width / cellSize; 39 | final numCellsY = size.height / cellSize; 40 | 41 | for (int row = 0; row < numCellsY; row++) { 42 | for (int col = 0; col < numCellsX; col++) { 43 | final color = (row + col) % 2 == 0 ? primary : secondary; 44 | canvas.drawRect( 45 | Rect.fromLTWH(col * cellSize, row * cellSize, cellSize, cellSize), 46 | Paint()..color = color, 47 | ); 48 | } 49 | } 50 | } 51 | 52 | @override 53 | bool shouldRepaint(covariant CustomPainter oldDelegate) { 54 | return false; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/pro_image_editor/shared/widgets/prepare_image_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// A widget that displays a loading screen with a progress indicator and a 4 | /// message. 5 | /// 6 | /// Used to inform the user that the editor is preparing a demo image. 7 | /// 8 | /// The UI consists of: 9 | /// - A circular progress indicator. 10 | /// - A message prompting the user to wait, styled with white text on a dark 11 | /// background. 12 | class PrepareImageWidget extends StatelessWidget { 13 | /// Creates a [PrepareImageWidget]. 14 | /// 15 | /// The widget is stateless and serves as a loading screen 16 | /// with a progress indicator and a message. 17 | const PrepareImageWidget({super.key}); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return const Material( 22 | color: Color.fromARGB(255, 19, 21, 22), 23 | child: Padding( 24 | padding: EdgeInsets.all(12.0), 25 | child: Column( 26 | crossAxisAlignment: CrossAxisAlignment.center, 27 | mainAxisAlignment: MainAxisAlignment.center, 28 | children: [ 29 | CircularProgressIndicator( 30 | color: Colors.white, 31 | ), 32 | SizedBox(height: 24), // Spacing between the widgets 33 | Text( 34 | 'Please wait...\nThe editor is preparing the demo image', 35 | textAlign: TextAlign.center, 36 | style: TextStyle( 37 | color: Colors.white, 38 | fontSize: 20, 39 | ), 40 | ), 41 | ], 42 | ), 43 | ), 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/provider/color_palette_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ColorPaletteProvider extends ChangeNotifier { 4 | List _colors = []; 5 | 6 | List get colors => _colors; 7 | 8 | void updateColors(List newColors) { 9 | _colors = newColors; 10 | notifyListeners(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/provider/getitlocator.dart: -------------------------------------------------------------------------------- 1 | import 'package:get_it/get_it.dart'; 2 | import 'package:magic_epaper_app/provider/color_palette_provider.dart'; 3 | 4 | final GetIt getIt = GetIt.instance; 5 | 6 | void setupLocator() { 7 | getIt.registerLazySingleton( 8 | () => ColorPaletteProvider()); 9 | } 10 | -------------------------------------------------------------------------------- /lib/provider/image_loader.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:image/image.dart' as img; 5 | import 'package:image_cropper/image_cropper.dart'; 6 | import 'package:image_picker/image_picker.dart'; 7 | 8 | class ImageLoader extends ChangeNotifier { 9 | img.Image? image; 10 | final List processedImgs = List.empty(growable: true); 11 | 12 | void pickImage({required int width, required int height}) async { 13 | final ImagePicker picker = ImagePicker(); 14 | final XFile? file = await picker.pickImage(source: ImageSource.gallery); 15 | if (file == null) return; 16 | 17 | final croppedFile = await ImageCropper().cropImage( 18 | sourcePath: file.path, 19 | aspectRatio: 20 | CropAspectRatio(ratioX: width.toDouble(), ratioY: height.toDouble()), 21 | ); 22 | if (croppedFile == null) return; 23 | 24 | processedImgs.clear(); 25 | image = await img.decodeImageFile(croppedFile.path); 26 | 27 | notifyListeners(); 28 | } 29 | 30 | Future updateImage({ 31 | required Uint8List bytes, 32 | required int width, 33 | required int height, 34 | }) async { 35 | final decoded = img.decodeImage(bytes); 36 | if (decoded != null) { 37 | final resized = img.copyResize(decoded, width: width, height: height); 38 | image = resized; 39 | notifyListeners(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/src/localization/app_en.arb: -------------------------------------------------------------------------------- 1 | { 2 | "appTitle": "magic_epaper_app_2", 3 | "@appTitle": { 4 | "description": "The title of the application" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /lib/util/epd/driver/driver.dart: -------------------------------------------------------------------------------- 1 | abstract class Driver { 2 | String get driverName; 3 | List get transmissionLines; 4 | int get refresh; 5 | } 6 | -------------------------------------------------------------------------------- /lib/util/epd/driver/uc8253.dart: -------------------------------------------------------------------------------- 1 | import 'driver.dart'; 2 | 3 | // UC8253 commands/registers, 4 | // define in the epaper display controller (UC8253) reference manual 5 | class Uc8253 extends Driver { 6 | @override 7 | int get refresh => 0x12; 8 | @override 9 | get driverName => 'UC8253'; 10 | @override 11 | List get transmissionLines => [0x10, 0x13]; 12 | } 13 | -------------------------------------------------------------------------------- /lib/util/epd/epd.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'package:image/image.dart' as img; 3 | import 'package:flutter/material.dart'; 4 | import 'driver/driver.dart'; 5 | import 'package:magic_epaper_app/util/image_processing/image_processing.dart'; 6 | 7 | abstract class Epd { 8 | int get width; 9 | int get height; 10 | final processingMethods = []; 11 | String get name; 12 | String get modelId; 13 | String get imgPath; 14 | List get colors; 15 | Driver get controller; 16 | String get driverName => controller.driverName; 17 | 18 | Uint8List _extractEpaperColorFrame(Color color, img.Image orgImage) { 19 | final image = ImageProcessing.extract(color, orgImage); 20 | final red = (color.r * 255).toInt(); 21 | final green = (color.g * 255).toInt(); 22 | final blue = (color.b * 255).toInt(); 23 | final colorPixel = img.ColorRgb8(red, green, blue); 24 | List bytes = List.empty(growable: true); 25 | int j = 0; 26 | int byte = 0; 27 | 28 | for (final pixel in image) { 29 | var bin = pixel.rNormalized - 30 | colorPixel.rNormalized + 31 | pixel.gNormalized - 32 | colorPixel.gNormalized + 33 | pixel.bNormalized - 34 | colorPixel.bNormalized; 35 | 36 | if (bin > 0.5) { 37 | byte |= 0x80 >> j; 38 | } 39 | 40 | j++; 41 | if (j >= 8) { 42 | bytes.add(byte); 43 | byte = 0; 44 | j = 0; 45 | } 46 | } 47 | 48 | return Uint8List.fromList(bytes); 49 | } 50 | 51 | List extractEpaperColorFrames(img.Image orgImage) { 52 | final retList = []; 53 | for (final c in colors) { 54 | if (c == Colors.white) continue; // skip white 55 | retList.add(_extractEpaperColorFrame(c, orgImage)); 56 | } 57 | return retList; 58 | } 59 | // TODO: howToAdjust ??? 60 | } 61 | -------------------------------------------------------------------------------- /lib/util/epd/gdey037z03.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:magic_epaper_app/constants/asset_paths.dart'; 3 | import 'package:magic_epaper_app/util/epd/driver/uc8253.dart'; 4 | import 'package:magic_epaper_app/util/image_processing/image_processing.dart'; 5 | import 'driver/driver.dart'; 6 | import 'epd.dart'; 7 | 8 | class Gdey037z03 extends Epd { 9 | @override 10 | get width => 240; // pixels 11 | 12 | @override 13 | get height => 416; // pixels 14 | 15 | @override 16 | String get name => 'E-Paper 3.7"'; 17 | @override 18 | String get modelId => 'GDEY037Z03'; 19 | @override 20 | String get imgPath => ImageAssets.epaper37Bwr; 21 | 22 | @override 23 | get colors => [Colors.white, Colors.black, Colors.red]; 24 | 25 | @override 26 | get controller => Uc8253() as Driver; 27 | 28 | Gdey037z03() { 29 | processingMethods.add(ImageProcessing.bwrFloydSteinbergDither); 30 | processingMethods.add(ImageProcessing.bwrFalseFloydSteinbergDither); 31 | processingMethods.add(ImageProcessing.bwrStuckiDither); 32 | processingMethods.add(ImageProcessing.bwrTriColorAtkinsonDither); 33 | processingMethods.add(ImageProcessing.bwrHalftone); 34 | processingMethods.add(ImageProcessing.bwrThreshold); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/util/epd/gdey037z03bw.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:magic_epaper_app/constants/asset_paths.dart'; 3 | import 'package:magic_epaper_app/util/epd/driver/uc8253.dart'; 4 | import 'package:magic_epaper_app/util/image_processing/image_processing.dart'; 5 | import 'driver/driver.dart'; 6 | import 'epd.dart'; 7 | 8 | class Gdey037z03BW extends Epd { 9 | @override 10 | get width => 240; // pixels 11 | 12 | @override 13 | get height => 416; // pixels 14 | 15 | @override 16 | String get name => 'E-Paper 3.7"'; 17 | @override 18 | String get modelId => 'GDEY037T03'; 19 | @override 20 | String get imgPath => ImageAssets.epaper37Bw; 21 | 22 | @override 23 | get colors => [Colors.white, Colors.black]; 24 | 25 | @override 26 | get controller => Uc8253() as Driver; 27 | 28 | Gdey037z03BW() { 29 | processingMethods.add(ImageProcessing.bwFloydSteinbergDither); 30 | processingMethods.add(ImageProcessing.bwFalseFloydSteinbergDither); 31 | processingMethods.add(ImageProcessing.bwStuckiDither); 32 | processingMethods.add(ImageProcessing.bwAtkinsonDither); 33 | processingMethods.add(ImageProcessing.bwHalftoneDither); 34 | processingMethods.add(ImageProcessing.bwThreshold); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/util/image_editor_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:image/image.dart' as img; 2 | import 'package:magic_epaper_app/util/epd/epd.dart'; 3 | 4 | List processImages({ 5 | required img.Image originalImage, 6 | required Epd epd, 7 | }) { 8 | final List processedImgs = []; 9 | 10 | img.Image transformed = img.copyResize( 11 | originalImage, 12 | width: epd.width, 13 | height: epd.height, 14 | ); 15 | 16 | for (final method in epd.processingMethods) { 17 | processedImgs.add(method(transformed)); 18 | } 19 | 20 | return processedImgs; 21 | } 22 | -------------------------------------------------------------------------------- /lib/util/image_processing/extract_quantizer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:image/image.dart' as img; 5 | 6 | class ExtractQuantizer extends img.Quantizer { 7 | final img.Palette _palette; 8 | late final HSVColor _toBeExtract; 9 | final double hThres, sThres, vThres; 10 | 11 | ExtractQuantizer({ 12 | Color toBeExtract = Colors.red, 13 | this.hThres = 40, 14 | this.sThres = 0.5, 15 | this.vThres = 0.5, 16 | }) : _palette = img.PaletteUint8(2, 3) { 17 | _palette.setRgb(0, (toBeExtract.r * 255).toInt(), 18 | (toBeExtract.g * 255).toInt(), (toBeExtract.b * 255).toInt()); 19 | _palette.setRgb(1, 255, 255, 255); 20 | _toBeExtract = HSVColor.fromColor(toBeExtract); 21 | } 22 | 23 | @override 24 | img.Palette get palette => _palette; 25 | 26 | @override 27 | img.Color getQuantizedColor(img.Color c) { 28 | return _isIt(c) 29 | ? img.ColorRgb8(_palette.getRed(0) as int, _palette.getGreen(0) as int, 30 | _palette.getBlue(0) as int) 31 | : img.ColorRgb8(_palette.getRed(1) as int, _palette.getGreen(1) as int, 32 | _palette.getBlue(1) as int); 33 | } 34 | 35 | @override 36 | int getColorIndex(img.Color c) => _isIt(c) ? 0 : 1; 37 | 38 | @override 39 | int getColorIndexRgb(int r, int g, int b) { 40 | return _isIt(img.ColorRgb8(r, g, b)) ? 0 : 1; 41 | } 42 | 43 | bool _inLinearRange(double v, double a, double b) { 44 | return a <= v && v <= b; 45 | } 46 | 47 | bool _inAngularRange(double v, double a, double b) { 48 | return cos((v - a) * (pi / 180)) > cos((b - a) * (pi / 180)); 49 | } 50 | 51 | bool _hsvInRange(HSVColor v, HSVColor a, HSVColor b) { 52 | return _inAngularRange(v.hue, a.hue, b.hue) && 53 | _inLinearRange(v.saturation, a.saturation, b.saturation) && 54 | _inLinearRange(v.value, a.value, b.value); 55 | } 56 | 57 | bool _hsvColorThreshold(HSVColor v, HSVColor p) { 58 | final hHalfThres = hThres / 2; 59 | final sHalfThres = sThres / 2; 60 | final vHalfThres = vThres / 2; 61 | 62 | final sLower = 63 | p.saturation - sHalfThres < 0.0 ? 0.0 : p.saturation - sHalfThres; 64 | final vLower = p.value - vHalfThres < 0.0 ? 0.0 : p.value - vHalfThres; 65 | final sUpper = 66 | p.saturation + sHalfThres > 1.0 ? 1.0 : p.saturation + sHalfThres; 67 | final vUpper = p.value + vHalfThres > 1.0 ? 1.0 : p.value + vHalfThres; 68 | 69 | final rangeA = 70 | HSVColor.fromAHSV(1, (p.hue - hHalfThres) % 360, sLower, vLower); 71 | final rangeB = 72 | HSVColor.fromAHSV(1, (p.hue + hHalfThres) % 360, sUpper, vUpper); 73 | 74 | return _hsvInRange(v, rangeA, rangeB); 75 | } 76 | 77 | bool _isIt(img.Color c) { 78 | final dartColor = Color.fromRGBO( 79 | c.r as int, c.g as int, c.b as int, c.aNormalized as double); 80 | final hsvColor = HSVColor.fromColor(dartColor); 81 | 82 | return _hsvColorThreshold(hsvColor, _toBeExtract); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/util/image_processing/image_processing.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:image/image.dart' as img; 3 | 4 | import 'extract_quantizer.dart'; 5 | import 'remap_quantizer.dart'; 6 | 7 | class ImageProcessing { 8 | static img.Image bwFloydSteinbergDither(img.Image orgImg) { 9 | var image = img.Image.from(orgImg); 10 | return img.ditherImage(image, quantizer: img.BinaryQuantizer()); 11 | } 12 | 13 | static img.Image bwFalseFloydSteinbergDither(img.Image orgImg) { 14 | var image = img.Image.from(orgImg); 15 | return img.ditherImage(image, 16 | quantizer: img.BinaryQuantizer(), 17 | kernel: img.DitherKernel.falseFloydSteinberg); 18 | } 19 | 20 | static img.Image bwStuckiDither(img.Image orgImg) { 21 | var image = img.Image.from(orgImg); 22 | return img.ditherImage(image, 23 | quantizer: img.BinaryQuantizer(), kernel: img.DitherKernel.stucki); 24 | } 25 | 26 | static img.Image bwAtkinsonDither(img.Image orgImg) { 27 | var image = img.Image.from(orgImg); 28 | return img.ditherImage(image, 29 | quantizer: img.BinaryQuantizer(), kernel: img.DitherKernel.atkinson); 30 | } 31 | 32 | static img.Image bwThreshold(img.Image orgImg) { 33 | var image = img.Image.from(orgImg); 34 | return img.ditherImage(image, 35 | quantizer: img.BinaryQuantizer(), kernel: img.DitherKernel.none); 36 | } 37 | 38 | static img.Image bwHalftoneDither(img.Image orgImg) { 39 | final image = img.Image.from(orgImg); 40 | img.grayscale(image); 41 | img.colorHalftone(image, size: 3); 42 | return img.ditherImage(image, quantizer: img.BinaryQuantizer()); 43 | } 44 | 45 | static img.Image bwrHalftone(img.Image orgImg) { 46 | var image = img.Image.from(orgImg); 47 | 48 | img.colorHalftone(image, size: 3); 49 | return img.ditherImage(image, 50 | quantizer: RemapQuantizer(palette: _createTriColorPalette()), 51 | kernel: img.DitherKernel.floydSteinberg); 52 | } 53 | 54 | static img.Image bwrFloydSteinbergDither(img.Image orgImg) { 55 | var image = img.Image.from(orgImg); 56 | 57 | return img.ditherImage(image, 58 | quantizer: RemapQuantizer(palette: _createTriColorPalette()), 59 | kernel: img.DitherKernel.floydSteinberg); 60 | } 61 | 62 | static img.Image bwrFalseFloydSteinbergDither(img.Image orgImg) { 63 | var image = img.Image.from(orgImg); 64 | 65 | return img.ditherImage(image, 66 | quantizer: RemapQuantizer(palette: _createTriColorPalette()), 67 | kernel: img.DitherKernel.falseFloydSteinberg); 68 | } 69 | 70 | static img.Image bwrStuckiDither(img.Image orgImg) { 71 | var image = img.Image.from(orgImg); 72 | 73 | return img.ditherImage(image, 74 | quantizer: RemapQuantizer(palette: _createTriColorPalette()), 75 | kernel: img.DitherKernel.stucki); 76 | } 77 | 78 | static img.Image bwrTriColorAtkinsonDither(img.Image orgImg) { 79 | var image = img.Image.from(orgImg); 80 | 81 | return img.ditherImage(image, 82 | quantizer: RemapQuantizer(palette: _createTriColorPalette()), 83 | kernel: img.DitherKernel.atkinson); 84 | } 85 | 86 | static img.Image extract(Color toBeExtract, img.Image orgImg) { 87 | var image = img.Image.from(orgImg); 88 | 89 | return img.ditherImage(image, 90 | quantizer: ExtractQuantizer(toBeExtract: toBeExtract, hThres: 80), 91 | kernel: img.DitherKernel.none); 92 | } 93 | 94 | static img.Image bwrThreshold(img.Image orgImg) { 95 | var image = img.Image.from(orgImg); 96 | 97 | return img.ditherImage(image, 98 | quantizer: RemapQuantizer(palette: _createTriColorPalette()), 99 | kernel: img.DitherKernel.none); 100 | } 101 | } 102 | 103 | img.PaletteUint8 _createTriColorPalette() { 104 | final palette = img.PaletteUint8(3, 3); 105 | palette.setRgb(0, 255, 0, 0); // red 106 | palette.setRgb(1, 0, 0, 0); // black 107 | palette.setRgb(2, 255, 255, 255); // white 108 | return palette; 109 | } 110 | -------------------------------------------------------------------------------- /lib/util/image_processing/remap_quantizer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'package:image/image.dart' as img; 3 | 4 | class RemapQuantizer extends img.Quantizer { 5 | @override 6 | final img.Palette palette; 7 | final _colorLut = []; 8 | 9 | RemapQuantizer({required this.palette}) { 10 | for (int i = 0; i < palette.numColors; i++) { 11 | _colorLut.add(img.ColorRgb8(palette.getRed(i) as int, 12 | palette.getGreen(i) as int, palette.getBlue(i) as int)); 13 | } 14 | } 15 | 16 | @override 17 | img.Color getQuantizedColor(img.Color c) { 18 | return _map(c).$1; 19 | } 20 | 21 | @override 22 | int getColorIndex(img.Color c) { 23 | return _map(c).$2; 24 | } 25 | 26 | @override 27 | int getColorIndexRgb(int r, int g, int b) { 28 | return _map(img.ColorRgb8(r, g, b)).$2; 29 | } 30 | 31 | num _distance(img.Color a, img.Color b) { 32 | return (a.r - b.r) * (a.r - b.r) + 33 | (a.g - b.g) * (a.g - b.g) + 34 | (a.b - b.b) * (a.b - b.b); 35 | } 36 | 37 | (img.Color, int) _map(img.Color c) { 38 | final ds = []; 39 | for (int i = 0; i < palette.numColors; i++) { 40 | ds.add(_distance(c, _colorLut[i])); 41 | } 42 | final nearestIndex = ds.indexOf(ds.reduce(min)); 43 | return (_colorLut[nearestIndex], nearestIndex); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/util/magic_epaper_firmware.dart: -------------------------------------------------------------------------------- 1 | import 'st25dv.dart'; 2 | 3 | class MagicEpaperFirmware { 4 | int get epdCmd => 0x00; // command packet, pull the epd's C/D pin to Low (CMD) 5 | int get epdSend => 0x01; // data packet, pull the epd's C/D pin to High (DATA) 6 | final tagChip = St25dv(); 7 | } 8 | -------------------------------------------------------------------------------- /lib/util/nfc_settings_launcher.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | class NFCSettingsLauncher { 5 | static const platform = MethodChannel('com.yourapp.nfc/settings'); 6 | 7 | static Future openNFCSettings() async { 8 | try { 9 | await platform.invokeMethod('openNFCSettings'); 10 | } on PlatformException catch (e) { 11 | debugPrint("Failed to open NFC settings: ${e.message}"); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/util/protocol.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:convert/convert.dart'; 5 | import 'package:flutter/widgets.dart'; 6 | import 'package:flutter_nfc_kit/flutter_nfc_kit.dart'; 7 | import 'package:fluttertoast/fluttertoast.dart'; 8 | import 'package:image/image.dart' as img; 9 | import 'package:magic_epaper_app/util/epd/epd.dart'; 10 | import 'package:app_settings/app_settings.dart'; 11 | import 'package:magic_epaper_app/util/magic_epaper_firmware.dart'; 12 | import 'package:magic_epaper_app/util/nfc_settings_launcher.dart'; 13 | 14 | class Protocol { 15 | final fw = MagicEpaperFirmware(); 16 | final Epd epd; 17 | final timeout = const Duration(seconds: 5); 18 | 19 | Protocol({required this.epd}); 20 | 21 | Future _transceive(nfcvCmd, Uint8List tagId, Uint8List msg) async { 22 | final raw = fw.tagChip.buildMessage(nfcvCmd, tagId, msg); 23 | return await FlutterNfcKit.transceive(raw, timeout: timeout); 24 | } 25 | 26 | Future _writeMsg(Uint8List tagId, Uint8List msg) async { 27 | return await _transceive(fw.tagChip.writeMsgCmd, tagId, msg); 28 | } 29 | 30 | Future _readDynCfg(Uint8List tagId, int address) async { 31 | final raw = fw.tagChip.buildReadDynCfgCmd(tagId, address); 32 | return await FlutterNfcKit.transceive(raw, timeout: timeout); 33 | } 34 | 35 | Future _writeDynCfg( 36 | Uint8List tagId, int address, int value) async { 37 | final raw = fw.tagChip.buildWriteDynCfgCmd(tagId, address, value); 38 | return await FlutterNfcKit.transceive(raw, timeout: timeout); 39 | } 40 | 41 | Future hasI2cGatheredMsg(Uint8List tagId) async { 42 | return ((await _readDynCfg(tagId, 0x0d)).elementAt(1) & 0x04) != 0x04; 43 | } 44 | 45 | Future enableEnergyHarvesting(Uint8List tagId) async { 46 | return await _writeDynCfg(tagId, 0x02, 0x01); 47 | } 48 | 49 | Future _sleep() async { 50 | await Future.delayed(const Duration(milliseconds: 20)); 51 | } 52 | 53 | Future wait4msgGathered(Uint8List tagId) async { 54 | var attempt = 4; 55 | while (attempt > 0) { 56 | try { 57 | if (await hasI2cGatheredMsg(tagId)) { 58 | return; // Exit successfully if message is gathered 59 | } 60 | } catch (e) { 61 | throw Exception("Error checking message: $e"); 62 | } 63 | attempt--; 64 | await _sleep(); // Wait before the next attempt 65 | } 66 | 67 | // If the loop completes without returning, it means the attempts timed out 68 | throw Exception("Timeout waiting for I2C message"); 69 | } 70 | 71 | Future writeFrame(Uint8List id, Uint8List frame, int cmd) async { 72 | final chunks = _split(data: frame); 73 | await _writeMsg( 74 | id, Uint8List.fromList([fw.epdCmd, cmd])); // enter transmission 1 75 | await _sleep(); 76 | for (int i = 0; i < chunks.length; i++) { 77 | Uint8List chunk = chunks[i]; 78 | debugPrint( 79 | "Writing chunk ${i + 1}/${chunks.length} len ${chunk.lengthInBytes}: ${chunk.map((e) => e.toRadixString(16)).toList()}"); 80 | 81 | await _writeMsg(id, chunk); 82 | await wait4msgGathered(id); 83 | } 84 | debugPrint("Transferred successfully."); 85 | } 86 | 87 | List _split({required Uint8List data, int chunkSize = 220}) { 88 | List chunks = []; 89 | for (int i = 0; i < data.length; i += chunkSize) { 90 | int end = (i + chunkSize > data.length) ? data.length : i + chunkSize; 91 | Uint8List chunk = 92 | Uint8List.fromList([fw.epdSend, ...data.sublist(i, end)]); 93 | chunks.add(chunk); 94 | } 95 | return chunks; 96 | } 97 | 98 | void writeImages(img.Image image) async { 99 | var availability = await FlutterNfcKit.nfcAvailability; 100 | switch (availability) { 101 | case NFCAvailability.available: 102 | break; 103 | case NFCAvailability.disabled: 104 | Fluttertoast.showToast(msg: "NFC is disabled. Please enable it."); 105 | if (Platform.isAndroid) { 106 | await NFCSettingsLauncher.openNFCSettings(); 107 | } else if (Platform.isIOS) { 108 | await AppSettings.openAppSettings(); 109 | } 110 | return; 111 | case NFCAvailability.not_supported: 112 | Fluttertoast.showToast(msg: "This device does not support NFC."); 113 | return; 114 | } 115 | 116 | Fluttertoast.showToast( 117 | msg: "Bring your phone near to the Magic Epaper Hardware"); 118 | debugPrint("Bring your phone near to the Magic Epaper Hardware"); 119 | final tag = await FlutterNfcKit.poll(timeout: timeout); 120 | debugPrint("Got a tag!"); 121 | 122 | var id = Uint8List.fromList(hex.decode(tag.id)); 123 | if (tag.type != NFCTagType.iso15693) { 124 | throw "Not a Magic Epaper Hardware"; 125 | } 126 | 127 | await enableEnergyHarvesting(id); 128 | await Future.delayed( 129 | const Duration(seconds: 2)); // waiting for the power supply stable 130 | 131 | final epdColors = epd.extractEpaperColorFrames(image); 132 | final transmissionLines = epd.controller.transmissionLines.iterator; 133 | for (final c in epdColors) { 134 | transmissionLines.moveNext(); 135 | await writeFrame(id, c, transmissionLines.current); 136 | } 137 | 138 | await _writeMsg( 139 | id, Uint8List.fromList([fw.epdCmd, epd.controller.refresh])); 140 | await FlutterNfcKit.finish(); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /lib/util/st25dv.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:flutter_nfc_kit/flutter_nfc_kit.dart'; 4 | 5 | // ST25DV commands/registers, define in the ST25DV reference manual 6 | class St25dv { 7 | int get writeMsgCmd => 0xaa; 8 | int get readMsgDmd => 0xac; 9 | int get readDynCfgCmd => 0xad; 10 | int get writeDynCfgCmd => 0xae; 11 | int get mfgCode => 0x02; // STMicroelectronics 12 | final defReqFlags = Iso15693RequestFlags( 13 | address: true, 14 | highDataRate: true, 15 | ); 16 | 17 | Uint8List buildIso15693Header(int cmd, Uint8List tagId) { 18 | final b = BytesBuilder(); 19 | b.addByte(defReqFlags.encode()); 20 | b.addByte(cmd); 21 | b.addByte(mfgCode); 22 | 23 | b.add(tagId); 24 | return b.toBytes(); 25 | } 26 | 27 | Uint8List buildMessage(int cmd, Uint8List tagId, Uint8List msg) { 28 | final b = BytesBuilder(); 29 | b.add(buildIso15693Header(cmd, tagId)); 30 | 31 | b.addByte(msg.lengthInBytes - 1); 32 | b.add(msg); 33 | 34 | return b.toBytes(); 35 | } 36 | 37 | Uint8List buildReadDynCfgCmd(Uint8List tagId, int address) { 38 | final b = BytesBuilder(); 39 | b.add(buildIso15693Header(readDynCfgCmd, tagId)); 40 | 41 | b.addByte(address); 42 | 43 | return b.toBytes(); 44 | } 45 | 46 | Uint8List buildWriteDynCfgCmd(Uint8List tagId, int address, int value) { 47 | final b = BytesBuilder(); 48 | b.add(buildIso15693Header(writeDynCfgCmd, tagId)); 49 | 50 | b.addByte(address); 51 | b.addByte(value); 52 | 53 | return b.toBytes(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/view/display_selection_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:magic_epaper_app/constants/color_constants.dart'; 3 | import 'package:magic_epaper_app/constants/string_constants.dart'; 4 | import 'package:magic_epaper_app/provider/getitlocator.dart'; 5 | import 'package:magic_epaper_app/util/epd/epd.dart'; 6 | import 'package:magic_epaper_app/util/epd/gdey037z03.dart'; 7 | import 'package:magic_epaper_app/util/epd/gdey037z03bw.dart'; 8 | import 'package:magic_epaper_app/view/image_editor.dart'; 9 | import 'package:provider/provider.dart'; 10 | import 'package:magic_epaper_app/provider/color_palette_provider.dart'; 11 | import 'package:magic_epaper_app/view/widget/display_card.dart'; 12 | 13 | class DisplaySelectionScreen extends StatefulWidget { 14 | const DisplaySelectionScreen({super.key}); 15 | 16 | @override 17 | State createState() => _DisplaySelectionScreenState(); 18 | } 19 | 20 | class _DisplaySelectionScreenState extends State { 21 | final List displays = [Gdey037z03(), Gdey037z03BW()]; 22 | int selectedIndex = -1; 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return ChangeNotifierProvider( 27 | create: (_) => getIt(), 28 | builder: (context, child) { 29 | return Scaffold( 30 | backgroundColor: Colors.white, 31 | appBar: AppBar( 32 | backgroundColor: colorAccent, 33 | elevation: 0, 34 | title: const Padding( 35 | padding: EdgeInsets.fromLTRB(5, 16, 16, 5), 36 | child: Column( 37 | crossAxisAlignment: CrossAxisAlignment.start, 38 | children: [ 39 | Text(StringConstants.appName, 40 | style: TextStyle( 41 | fontSize: 24, 42 | fontWeight: FontWeight.bold, 43 | color: Colors.white, 44 | )), 45 | SizedBox(height: 8), 46 | Text('Select your ePaper display type', 47 | style: TextStyle( 48 | fontSize: 16, 49 | color: Colors.white, 50 | )), 51 | ], 52 | ), 53 | ), 54 | toolbarHeight: 85, 55 | ), 56 | body: SafeArea( 57 | child: Padding( 58 | padding: const EdgeInsets.fromLTRB(10.0, 14, 16.0, 16.0), 59 | child: Column( 60 | children: [ 61 | Expanded( 62 | child: GridView.builder( 63 | gridDelegate: 64 | const SliverGridDelegateWithFixedCrossAxisCount( 65 | crossAxisCount: 2, 66 | childAspectRatio: 0.6, 67 | mainAxisSpacing: 8, 68 | crossAxisSpacing: 8, 69 | ), 70 | itemCount: displays.length, 71 | itemBuilder: (context, index) => DisplayCard( 72 | display: displays[index], 73 | isSelected: selectedIndex == index, 74 | onTap: () => setState(() => selectedIndex = index), 75 | ), 76 | ), 77 | ), 78 | _buildContinueButton(context), 79 | ], 80 | ), 81 | ), 82 | ), 83 | ); 84 | }); 85 | } 86 | 87 | Widget _buildContinueButton(BuildContext context) { 88 | final isEnabled = selectedIndex != -1; 89 | return SizedBox( 90 | width: double.infinity, 91 | height: 50, 92 | child: ElevatedButton( 93 | onPressed: isEnabled 94 | ? () { 95 | context.read().updateColors( 96 | displays[selectedIndex].colors, 97 | ); 98 | 99 | Navigator.push( 100 | context, 101 | MaterialPageRoute( 102 | builder: (context) => ImageEditor( 103 | epd: displays[selectedIndex], 104 | ), 105 | ), 106 | ); 107 | } 108 | : null, 109 | style: ElevatedButton.styleFrom( 110 | backgroundColor: colorPrimary.withAlpha(isEnabled ? 255 : 125), 111 | foregroundColor: Colors.white.withAlpha(isEnabled ? 255 : 178), 112 | shape: RoundedRectangleBorder( 113 | borderRadius: BorderRadius.circular(8.0), 114 | ), 115 | ), 116 | child: const Text( 117 | 'Continue', 118 | style: TextStyle( 119 | fontSize: 16, 120 | fontWeight: FontWeight.bold, 121 | ), 122 | ), 123 | ), 124 | ); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /lib/view/image_editor.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:magic_epaper_app/pro_image_editor/features/movable_background_image.dart'; 5 | import 'package:magic_epaper_app/view/widget/flip_controls.dart'; 6 | import 'package:magic_epaper_app/util/image_editor_utils.dart'; 7 | import 'package:magic_epaper_app/view/widget/image_list.dart'; 8 | import 'package:provider/provider.dart'; 9 | import 'package:image/image.dart' as img; 10 | 11 | import 'package:magic_epaper_app/provider/image_loader.dart'; 12 | import 'package:magic_epaper_app/util/epd/epd.dart'; 13 | import 'package:magic_epaper_app/constants/color_constants.dart'; 14 | 15 | class ImageEditor extends StatefulWidget { 16 | final Epd epd; 17 | const ImageEditor({super.key, required this.epd}); 18 | 19 | @override 20 | State createState() => _ImageEditorState(); 21 | } 22 | 23 | class _ImageEditorState extends State { 24 | bool flipHorizontal = false; 25 | bool flipVertical = false; 26 | 27 | void toggleFlipHorizontal() { 28 | setState(() { 29 | flipHorizontal = !flipHorizontal; 30 | }); 31 | } 32 | 33 | void toggleFlipVertical() { 34 | setState(() { 35 | flipVertical = !flipVertical; 36 | }); 37 | } 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | var imgLoader = context.watch(); 42 | final orgImg = imgLoader.image; 43 | 44 | final List processedImgs = orgImg != null 45 | ? processImages( 46 | originalImage: orgImg, 47 | epd: widget.epd, 48 | ) 49 | : []; 50 | 51 | final imgList = ImageList( 52 | imgList: processedImgs, 53 | epd: widget.epd, 54 | flipHorizontal: flipHorizontal, 55 | flipVertical: flipVertical, 56 | ); 57 | 58 | return Scaffold( 59 | backgroundColor: Colors.white, 60 | appBar: AppBar( 61 | iconTheme: const IconThemeData( 62 | color: Colors.white, 63 | ), 64 | backgroundColor: colorAccent, 65 | elevation: 0, 66 | title: const Center( 67 | child: Padding( 68 | padding: EdgeInsets.symmetric(vertical: 16.0), 69 | child: Text( 70 | 'Select Your Filter', 71 | textAlign: TextAlign.center, 72 | style: TextStyle( 73 | fontSize: 16, 74 | fontWeight: FontWeight.bold, 75 | color: Colors.white, 76 | ), 77 | ), 78 | ), 79 | ), 80 | toolbarHeight: 85, 81 | actions: [ 82 | Padding( 83 | padding: 84 | const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), 85 | child: TextButton( 86 | style: TextButton.styleFrom( 87 | backgroundColor: Colors.white.withValues(alpha: 0.2), 88 | foregroundColor: Colors.white, 89 | shape: RoundedRectangleBorder( 90 | borderRadius: BorderRadius.circular(8), 91 | ), 92 | ), 93 | onPressed: () { 94 | imgLoader.pickImage( 95 | width: widget.epd.width, height: widget.epd.height); 96 | }, 97 | child: const Text( 98 | "Import Image", 99 | style: TextStyle(fontWeight: FontWeight.bold), 100 | ), 101 | ), 102 | ), 103 | TextButton( 104 | onPressed: () async { 105 | final canvasBytes = await Navigator.of(context).push( 106 | MaterialPageRoute( 107 | builder: (context) => const MovableBackgroundImageExample(), 108 | ), 109 | ); 110 | imgLoader.updateImage( 111 | bytes: canvasBytes!, 112 | width: widget.epd.width, 113 | height: widget.epd.height, 114 | ); 115 | }, 116 | child: const Text("Open Editor"), 117 | ), 118 | ], 119 | ), 120 | floatingActionButton: orgImg != null 121 | ? FlipControls( 122 | onFlipHorizontal: toggleFlipHorizontal, 123 | onFlipVertical: toggleFlipVertical, 124 | ) 125 | : null, 126 | body: SafeArea( 127 | child: Padding( 128 | padding: const EdgeInsets.symmetric(horizontal: 8.0), 129 | child: imgList, 130 | ), 131 | ), 132 | ); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /lib/view/widget/color_dot.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ColorDot extends StatelessWidget { 4 | final Color color; 5 | final double size; 6 | 7 | const ColorDot({ 8 | super.key, 9 | required this.color, 10 | this.size = 12.0, 11 | }); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Container( 16 | width: size, 17 | height: size, 18 | margin: const EdgeInsets.symmetric(horizontal: 2.0), 19 | decoration: BoxDecoration( 20 | color: color, 21 | shape: BoxShape.circle, 22 | border: Border.all( 23 | color: Colors.grey.shade300, 24 | width: 1.0, 25 | ), 26 | ), 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/view/widget/display_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:magic_epaper_app/constants/color_constants.dart'; 3 | import 'package:magic_epaper_app/util/epd/epd.dart'; 4 | import 'package:magic_epaper_app/view/widget/color_dot.dart'; 5 | 6 | class DisplayCard extends StatelessWidget { 7 | final Epd display; 8 | final bool isSelected; 9 | final VoidCallback onTap; 10 | 11 | const DisplayCard({ 12 | super.key, 13 | required this.display, 14 | required this.isSelected, 15 | required this.onTap, 16 | }); 17 | 18 | String _getColorName(Color color) { 19 | switch (color) { 20 | case Colors.black: 21 | return 'Black'; 22 | case Colors.white: 23 | return 'White'; 24 | case Colors.red: 25 | return 'Red'; 26 | case Colors.yellow: 27 | return 'Yellow'; 28 | case Colors.orange: 29 | return 'Orange'; 30 | case Colors.green: 31 | return 'Green'; 32 | case Colors.blue: 33 | return 'Blue'; 34 | default: 35 | return ''; 36 | } 37 | } 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | return InkWell( 42 | onTap: onTap, 43 | highlightColor: Colors.redAccent, 44 | borderRadius: BorderRadius.circular(12), 45 | splashColor: Colors.redAccent.withValues(alpha: 0.2), 46 | child: Card( 47 | color: Colors.white, 48 | elevation: isSelected ? 4 : 1, 49 | shape: RoundedRectangleBorder( 50 | borderRadius: BorderRadius.circular(12), 51 | side: BorderSide( 52 | color: isSelected ? colorPrimary : Colors.grey.shade300, 53 | width: isSelected ? 2 : 1, 54 | ), 55 | ), 56 | child: Column( 57 | crossAxisAlignment: CrossAxisAlignment.start, 58 | children: [ 59 | Expanded( 60 | child: ClipRRect( 61 | borderRadius: const BorderRadius.only( 62 | topLeft: Radius.circular(11), 63 | topRight: Radius.circular(11), 64 | ), 65 | child: Container( 66 | width: double.infinity, 67 | color: const Color.fromARGB(255, 255, 255, 255), 68 | child: Padding( 69 | padding: const EdgeInsets.all(4.0), 70 | child: Image.asset( 71 | display.imgPath, 72 | fit: BoxFit.contain, 73 | height: 160, 74 | errorBuilder: (context, error, stackTrace) { 75 | return Center( 76 | child: Icon( 77 | Icons.display_settings, 78 | size: 60, 79 | color: Colors.grey.shade400, 80 | ), 81 | ); 82 | }, 83 | ), 84 | ), 85 | ), 86 | ), 87 | ), 88 | Padding( 89 | padding: const EdgeInsets.all(12.0), 90 | child: Column( 91 | crossAxisAlignment: CrossAxisAlignment.start, 92 | children: [ 93 | Text( 94 | display.name, 95 | style: const TextStyle( 96 | fontWeight: FontWeight.bold, 97 | fontSize: 16, 98 | ), 99 | ), 100 | const SizedBox(height: 8), 101 | Row( 102 | children: display.colors 103 | .map((color) => ColorDot(color: color)) 104 | .toList(), 105 | ), 106 | const SizedBox(height: 4), 107 | Text( 108 | display.colors.map(_getColorName).join(', '), 109 | style: const TextStyle( 110 | fontSize: 10, 111 | color: Colors.grey, 112 | ), 113 | ), 114 | const SizedBox(height: 8), 115 | _buildSpecRow('Model:', display.modelId), 116 | _buildSpecRow( 117 | 'Resolution:', '${display.width} × ${display.height}'), 118 | _buildSpecRow('Driver:', display.driverName), 119 | ], 120 | ), 121 | ), 122 | ], 123 | ), 124 | ), 125 | ); 126 | } 127 | 128 | Widget _buildSpecRow(String label, String value) { 129 | return Padding( 130 | padding: const EdgeInsets.only(bottom: 4), 131 | child: Row( 132 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 133 | children: [ 134 | Text(label, style: const TextStyle(fontSize: 10, color: Colors.grey)), 135 | Text(value, 136 | style: 137 | const TextStyle(fontSize: 10, fontWeight: FontWeight.w500)), 138 | ], 139 | ), 140 | ); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /lib/view/widget/flip_controls.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:magic_epaper_app/constants/asset_paths.dart'; 3 | 4 | class FlipControls extends StatelessWidget { 5 | final VoidCallback onFlipHorizontal; 6 | final VoidCallback onFlipVertical; 7 | 8 | const FlipControls({ 9 | super.key, 10 | required this.onFlipHorizontal, 11 | required this.onFlipVertical, 12 | }); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Padding( 17 | padding: const EdgeInsets.only(bottom: 80.0), 18 | child: Column( 19 | mainAxisAlignment: MainAxisAlignment.end, 20 | children: [ 21 | FloatingActionButton( 22 | backgroundColor: Colors.white, 23 | heroTag: 'flipH', 24 | onPressed: onFlipHorizontal, 25 | tooltip: 'Flip Horizontally', 26 | child: Image.asset( 27 | ImageAssets.flipHorizontal, 28 | height: 24, 29 | width: 24, 30 | ), 31 | ), 32 | const SizedBox(height: 10), 33 | FloatingActionButton( 34 | backgroundColor: Colors.white, 35 | heroTag: 'flipV', 36 | onPressed: onFlipVertical, 37 | tooltip: 'Flip Vertically', 38 | child: Transform.rotate( 39 | angle: -1.5708, 40 | child: Image.asset( 41 | ImageAssets.flipHorizontal, 42 | height: 24, 43 | width: 24, 44 | ), 45 | ), 46 | ), 47 | ], 48 | ), 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.10) 3 | project(runner LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "magic_epaper_app") 8 | # The unique GTK application identifier for this application. See: 9 | # https://wiki.gnome.org/HowDoI/ChooseApplicationID 10 | set(APPLICATION_ID "org.fossasia.magic_epaper_app") 11 | 12 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 13 | # versions of CMake. 14 | cmake_policy(SET CMP0063 NEW) 15 | 16 | # Load bundled libraries from the lib/ directory relative to the binary. 17 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 18 | 19 | # Root filesystem for cross-building. 20 | if(FLUTTER_TARGET_PLATFORM_SYSROOT) 21 | set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) 22 | set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) 23 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 24 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) 25 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 26 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 27 | endif() 28 | 29 | # Define build configuration options. 30 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 31 | set(CMAKE_BUILD_TYPE "Debug" CACHE 32 | STRING "Flutter build mode" FORCE) 33 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 34 | "Debug" "Profile" "Release") 35 | endif() 36 | 37 | # Compilation settings that should be applied to most targets. 38 | # 39 | # Be cautious about adding new options here, as plugins use this function by 40 | # default. In most cases, you should add new options to specific targets instead 41 | # of modifying this function. 42 | function(APPLY_STANDARD_SETTINGS TARGET) 43 | target_compile_features(${TARGET} PUBLIC cxx_std_14) 44 | target_compile_options(${TARGET} PRIVATE -Wall -Werror) 45 | target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") 46 | target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") 47 | endfunction() 48 | 49 | # Flutter library and tool build rules. 50 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 51 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 52 | 53 | # System-level dependencies. 54 | find_package(PkgConfig REQUIRED) 55 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 56 | 57 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") 58 | 59 | # Define the application target. To change its name, change BINARY_NAME above, 60 | # not the value here, or `flutter run` will no longer work. 61 | # 62 | # Any new source files that you add to the application should be added here. 63 | add_executable(${BINARY_NAME} 64 | "main.cc" 65 | "my_application.cc" 66 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 67 | ) 68 | 69 | # Apply the standard set of build settings. This can be removed for applications 70 | # that need different build settings. 71 | apply_standard_settings(${BINARY_NAME}) 72 | 73 | # Add dependency libraries. Add any application-specific dependencies here. 74 | target_link_libraries(${BINARY_NAME} PRIVATE flutter) 75 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) 76 | 77 | # Run the Flutter tool portions of the build. This must not be removed. 78 | add_dependencies(${BINARY_NAME} flutter_assemble) 79 | 80 | # Only the install-generated bundle's copy of the executable will launch 81 | # correctly, since the resources must in the right relative locations. To avoid 82 | # people trying to run the unbundled copy, put it in a subdirectory instead of 83 | # the default top-level location. 84 | set_target_properties(${BINARY_NAME} 85 | PROPERTIES 86 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" 87 | ) 88 | 89 | 90 | # Generated plugin build rules, which manage building the plugins and adding 91 | # them to the application. 92 | include(flutter/generated_plugins.cmake) 93 | 94 | 95 | # === Installation === 96 | # By default, "installing" just makes a relocatable bundle in the build 97 | # directory. 98 | set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") 99 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 100 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 101 | endif() 102 | 103 | # Start with a clean build bundle directory every time. 104 | install(CODE " 105 | file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") 106 | " COMPONENT Runtime) 107 | 108 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 109 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") 110 | 111 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 112 | COMPONENT Runtime) 113 | 114 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 115 | COMPONENT Runtime) 116 | 117 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 118 | COMPONENT Runtime) 119 | 120 | foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) 121 | install(FILES "${bundled_library}" 122 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 123 | COMPONENT Runtime) 124 | endforeach(bundled_library) 125 | 126 | # Copy the native assets provided by the build.dart from all packages. 127 | set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") 128 | install(DIRECTORY "${NATIVE_ASSETS_DIR}" 129 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 130 | COMPONENT Runtime) 131 | 132 | # Fully re-copy the assets directory on each build to avoid having stale files 133 | # from a previous install. 134 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 135 | install(CODE " 136 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 137 | " COMPONENT Runtime) 138 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 139 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 140 | 141 | # Install the AOT library on non-Debug builds only. 142 | if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") 143 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 144 | COMPONENT Runtime) 145 | endif() 146 | -------------------------------------------------------------------------------- /linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.10) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | 12 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 13 | # which isn't available in 3.10. 14 | function(list_prepend LIST_NAME PREFIX) 15 | set(NEW_LIST "") 16 | foreach(element ${${LIST_NAME}}) 17 | list(APPEND NEW_LIST "${PREFIX}${element}") 18 | endforeach(element) 19 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 20 | endfunction() 21 | 22 | # === Flutter Library === 23 | # System-level dependencies. 24 | find_package(PkgConfig REQUIRED) 25 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 26 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 27 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 28 | 29 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 30 | 31 | # Published to parent scope for install step. 32 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 33 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 34 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 35 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 36 | 37 | list(APPEND FLUTTER_LIBRARY_HEADERS 38 | "fl_basic_message_channel.h" 39 | "fl_binary_codec.h" 40 | "fl_binary_messenger.h" 41 | "fl_dart_project.h" 42 | "fl_engine.h" 43 | "fl_json_message_codec.h" 44 | "fl_json_method_codec.h" 45 | "fl_message_codec.h" 46 | "fl_method_call.h" 47 | "fl_method_channel.h" 48 | "fl_method_codec.h" 49 | "fl_method_response.h" 50 | "fl_plugin_registrar.h" 51 | "fl_plugin_registry.h" 52 | "fl_standard_message_codec.h" 53 | "fl_standard_method_codec.h" 54 | "fl_string_codec.h" 55 | "fl_value.h" 56 | "fl_view.h" 57 | "flutter_linux.h" 58 | ) 59 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 60 | add_library(flutter INTERFACE) 61 | target_include_directories(flutter INTERFACE 62 | "${EPHEMERAL_DIR}" 63 | ) 64 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 65 | target_link_libraries(flutter INTERFACE 66 | PkgConfig::GTK 67 | PkgConfig::GLIB 68 | PkgConfig::GIO 69 | ) 70 | add_dependencies(flutter flutter_assemble) 71 | 72 | # === Flutter tool backend === 73 | # _phony_ is a non-existent file to force this command to run every time, 74 | # since currently there's no way to get a full input/output list from the 75 | # flutter tool. 76 | add_custom_command( 77 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 78 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 79 | COMMAND ${CMAKE_COMMAND} -E env 80 | ${FLUTTER_TOOL_ENVIRONMENT} 81 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 82 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 83 | VERBATIM 84 | ) 85 | add_custom_target(flutter_assemble DEPENDS 86 | "${FLUTTER_LIBRARY}" 87 | ${FLUTTER_LIBRARY_HEADERS} 88 | ) 89 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | 11 | void fl_register_plugins(FlPluginRegistry* registry) { 12 | g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = 13 | fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); 14 | file_selector_plugin_register_with_registrar(file_selector_linux_registrar); 15 | } 16 | -------------------------------------------------------------------------------- /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 | file_selector_linux 7 | ) 8 | 9 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 10 | ) 11 | 12 | set(PLUGIN_BUNDLED_LIBRARIES) 13 | 14 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 15 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 16 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 19 | endforeach(plugin) 20 | 21 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 22 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 23 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 24 | endforeach(ffi_plugin) 25 | -------------------------------------------------------------------------------- /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.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | #include 4 | #ifdef GDK_WINDOWING_X11 5 | #include 6 | #endif 7 | 8 | #include "flutter/generated_plugin_registrant.h" 9 | 10 | struct _MyApplication { 11 | GtkApplication parent_instance; 12 | char** dart_entrypoint_arguments; 13 | }; 14 | 15 | G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) 16 | 17 | // Implements GApplication::activate. 18 | static void my_application_activate(GApplication* application) { 19 | MyApplication* self = MY_APPLICATION(application); 20 | GtkWindow* window = 21 | GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); 22 | 23 | // Use a header bar when running in GNOME as this is the common style used 24 | // by applications and is the setup most users will be using (e.g. Ubuntu 25 | // desktop). 26 | // If running on X and not using GNOME then just use a traditional title bar 27 | // in case the window manager does more exotic layout, e.g. tiling. 28 | // If running on Wayland assume the header bar will work (may need changing 29 | // if future cases occur). 30 | gboolean use_header_bar = TRUE; 31 | #ifdef GDK_WINDOWING_X11 32 | GdkScreen* screen = gtk_window_get_screen(window); 33 | if (GDK_IS_X11_SCREEN(screen)) { 34 | const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); 35 | if (g_strcmp0(wm_name, "GNOME Shell") != 0) { 36 | use_header_bar = FALSE; 37 | } 38 | } 39 | #endif 40 | if (use_header_bar) { 41 | GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); 42 | gtk_widget_show(GTK_WIDGET(header_bar)); 43 | gtk_header_bar_set_title(header_bar, "magic_epaper_app"); 44 | gtk_header_bar_set_show_close_button(header_bar, TRUE); 45 | gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); 46 | } else { 47 | gtk_window_set_title(window, "magic_epaper_app"); 48 | } 49 | 50 | gtk_window_set_default_size(window, 1280, 720); 51 | gtk_widget_show(GTK_WIDGET(window)); 52 | 53 | g_autoptr(FlDartProject) project = fl_dart_project_new(); 54 | fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); 55 | 56 | FlView* view = fl_view_new(project); 57 | gtk_widget_show(GTK_WIDGET(view)); 58 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); 59 | 60 | fl_register_plugins(FL_PLUGIN_REGISTRY(view)); 61 | 62 | gtk_widget_grab_focus(GTK_WIDGET(view)); 63 | } 64 | 65 | // Implements GApplication::local_command_line. 66 | static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { 67 | MyApplication* self = MY_APPLICATION(application); 68 | // Strip out the first argument as it is the binary name. 69 | self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); 70 | 71 | g_autoptr(GError) error = nullptr; 72 | if (!g_application_register(application, nullptr, &error)) { 73 | g_warning("Failed to register: %s", error->message); 74 | *exit_status = 1; 75 | return TRUE; 76 | } 77 | 78 | g_application_activate(application); 79 | *exit_status = 0; 80 | 81 | return TRUE; 82 | } 83 | 84 | // Implements GApplication::startup. 85 | static void my_application_startup(GApplication* application) { 86 | //MyApplication* self = MY_APPLICATION(object); 87 | 88 | // Perform any actions required at application startup. 89 | 90 | G_APPLICATION_CLASS(my_application_parent_class)->startup(application); 91 | } 92 | 93 | // Implements GApplication::shutdown. 94 | static void my_application_shutdown(GApplication* application) { 95 | //MyApplication* self = MY_APPLICATION(object); 96 | 97 | // Perform any actions required at application shutdown. 98 | 99 | G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); 100 | } 101 | 102 | // Implements GObject::dispose. 103 | static void my_application_dispose(GObject* object) { 104 | MyApplication* self = MY_APPLICATION(object); 105 | g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); 106 | G_OBJECT_CLASS(my_application_parent_class)->dispose(object); 107 | } 108 | 109 | static void my_application_class_init(MyApplicationClass* klass) { 110 | G_APPLICATION_CLASS(klass)->activate = my_application_activate; 111 | G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; 112 | G_APPLICATION_CLASS(klass)->startup = my_application_startup; 113 | G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; 114 | G_OBJECT_CLASS(klass)->dispose = my_application_dispose; 115 | } 116 | 117 | static void my_application_init(MyApplication* self) {} 118 | 119 | MyApplication* my_application_new() { 120 | return MY_APPLICATION(g_object_new(my_application_get_type(), 121 | "application-id", APPLICATION_ID, 122 | "flags", G_APPLICATION_NON_UNIQUE, 123 | nullptr)); 124 | } 125 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /macos/.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 "ephemeral/Flutter-Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "ephemeral/Flutter-Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import app_settings 9 | import file_selector_macos 10 | 11 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 12 | AppSettingsPlugin.register(with: registry.registrar(forPlugin: "AppSettingsPlugin")) 13 | FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) 14 | } 15 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/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 = magic_epaper_app 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = org.fossasia.magicEpaperApp 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2024 org.fossasia. 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 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: magic_epaper_app 2 | description: "A new Flutter project." 3 | 4 | # Prevent accidental publishing to pub.dev. 5 | publish_to: "none" 6 | 7 | version: 1.0.0+1 8 | 9 | environment: 10 | sdk: ">=3.3.4 <4.0.0" 11 | flutter: "3.29.2" 12 | 13 | dependencies: 14 | flutter: 15 | sdk: flutter 16 | flutter_localizations: 17 | sdk: flutter 18 | 19 | english_words: ^4.0.0 20 | provider: ^6.0.0 21 | # nfc_manager: ^3.5.0 22 | flutter_nfc_kit: ^3.5.2 23 | convert: ^3.1.2 24 | flutter_svg_provider: ^1.0.7 25 | matrix2d: ^1.0.4 26 | image: ^4.3.0 27 | image_picker: ^1.1.2 28 | image_cropper: ^9.0.0 29 | app_settings: ^6.1.1 30 | fluttertoast: ^8.2.12 31 | pro_image_editor: ^9.4.1 32 | vibration: ^3.1.3 33 | gal: ^2.3.1 34 | file_saver: ^0.2.14 35 | bot_toast: ^4.1.3 36 | media_kit: ^1.2.0 37 | url_launcher: ^6.3.1 38 | google_fonts: ^6.2.1 39 | file_picker: ^10.1.2 40 | flutter_colorpicker: ^1.1.0 41 | mime: ^2.0.0 42 | intl: ^0.19.0 43 | path_provider: ^2.0.15 44 | get_it: ^8.0.3 45 | 46 | dev_dependencies: 47 | flutter_test: 48 | sdk: flutter 49 | 50 | flutter_lints: ^4.0.0 51 | 52 | flutter: 53 | uses-material-design: true 54 | 55 | # Enable generation of localized Strings from arb files. 56 | generate: true 57 | 58 | assets: 59 | # Add assets from the images directory to the application. 60 | - assets/images/ 61 | - assets/images/displays/ 62 | - assets/canvas/ 63 | -------------------------------------------------------------------------------- /test/unit_test.dart: -------------------------------------------------------------------------------- 1 | // This is an example unit test. 2 | // 3 | // A unit test tests a single function, method, or class. To learn more about 4 | // writing unit tests, visit 5 | // https://flutter.dev/to/unit-testing 6 | 7 | import 'package:flutter_test/flutter_test.dart'; 8 | 9 | void main() { 10 | group('Plus Operator', () { 11 | test('should add two numbers together', () { 12 | expect(1 + 1, 2); 13 | }); 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is an example Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility in the flutter_test package. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | // 8 | // Visit https://flutter.dev/to/widget-testing for 9 | // more information about Widget testing. 10 | 11 | import 'package:flutter/material.dart'; 12 | import 'package:flutter_test/flutter_test.dart'; 13 | 14 | void main() { 15 | group('MyWidget', () { 16 | testWidgets('should display a string of text', (WidgetTester tester) async { 17 | // Define a Widget 18 | const myWidget = MaterialApp( 19 | home: Scaffold( 20 | body: Text('Hello'), 21 | ), 22 | ); 23 | 24 | // Build myWidget and trigger a frame. 25 | await tester.pumpWidget(myWidget); 26 | 27 | // Verify myWidget shows some text 28 | expect(find.byType(Text), findsOneWidget); 29 | }); 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | magic_epaper_app 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "magic_epaper_app", 3 | "short_name": "magic_epaper_app", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "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 | -------------------------------------------------------------------------------- /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/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.14) 3 | project(magic_epaper_app LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "magic_epaper_app") 8 | 9 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 10 | # versions of CMake. 11 | cmake_policy(VERSION 3.14...3.25) 12 | 13 | # Define build configuration option. 14 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 15 | if(IS_MULTICONFIG) 16 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 17 | CACHE STRING "" FORCE) 18 | else() 19 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 20 | set(CMAKE_BUILD_TYPE "Debug" CACHE 21 | STRING "Flutter build mode" FORCE) 22 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 23 | "Debug" "Profile" "Release") 24 | endif() 25 | endif() 26 | # Define settings for the Profile build mode. 27 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 28 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 29 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 30 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 31 | 32 | # Use Unicode for all projects. 33 | add_definitions(-DUNICODE -D_UNICODE) 34 | 35 | # Compilation settings that should be applied to most targets. 36 | # 37 | # Be cautious about adding new options here, as plugins use this function by 38 | # default. In most cases, you should add new options to specific targets instead 39 | # of modifying this function. 40 | function(APPLY_STANDARD_SETTINGS TARGET) 41 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 42 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 43 | target_compile_options(${TARGET} PRIVATE /EHsc) 44 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 45 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 46 | endfunction() 47 | 48 | # Flutter library and tool build rules. 49 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 50 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 51 | 52 | # Application build; see runner/CMakeLists.txt. 53 | add_subdirectory("runner") 54 | 55 | 56 | # Generated plugin build rules, which manage building the plugins and adding 57 | # them to the application. 58 | include(flutter/generated_plugins.cmake) 59 | 60 | 61 | # === Installation === 62 | # Support files are copied into place next to the executable, so that it can 63 | # run in place. This is done instead of making a separate bundle (as on Linux) 64 | # so that building and running from within Visual Studio will work. 65 | set(BUILD_BUNDLE_DIR "$") 66 | # Make the "install" step default, as it's required to run. 67 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 68 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 69 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 70 | endif() 71 | 72 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 73 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 74 | 75 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 76 | COMPONENT Runtime) 77 | 78 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 79 | COMPONENT Runtime) 80 | 81 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 82 | COMPONENT Runtime) 83 | 84 | if(PLUGIN_BUNDLED_LIBRARIES) 85 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 86 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 87 | COMPONENT Runtime) 88 | endif() 89 | 90 | # Copy the native assets provided by the build.dart from all packages. 91 | set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") 92 | install(DIRECTORY "${NATIVE_ASSETS_DIR}" 93 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 94 | COMPONENT Runtime) 95 | 96 | # Fully re-copy the assets directory on each build to avoid having stale files 97 | # from a previous install. 98 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 99 | install(CODE " 100 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 101 | " COMPONENT Runtime) 102 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 103 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 104 | 105 | # Install the AOT library on non-Debug builds only. 106 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 107 | CONFIGURATIONS Profile;Release 108 | COMPONENT Runtime) 109 | -------------------------------------------------------------------------------- /windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.14) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 12 | 13 | # Set fallback configurations for older versions of the flutter tool. 14 | if (NOT DEFINED FLUTTER_TARGET_PLATFORM) 15 | set(FLUTTER_TARGET_PLATFORM "windows-x64") 16 | endif() 17 | 18 | # === Flutter Library === 19 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 20 | 21 | # Published to parent scope for install step. 22 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 23 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 24 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 25 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 26 | 27 | list(APPEND FLUTTER_LIBRARY_HEADERS 28 | "flutter_export.h" 29 | "flutter_windows.h" 30 | "flutter_messenger.h" 31 | "flutter_plugin_registrar.h" 32 | "flutter_texture_registrar.h" 33 | ) 34 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 35 | add_library(flutter INTERFACE) 36 | target_include_directories(flutter INTERFACE 37 | "${EPHEMERAL_DIR}" 38 | ) 39 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 40 | add_dependencies(flutter flutter_assemble) 41 | 42 | # === Wrapper === 43 | list(APPEND CPP_WRAPPER_SOURCES_CORE 44 | "core_implementations.cc" 45 | "standard_codec.cc" 46 | ) 47 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 48 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 49 | "plugin_registrar.cc" 50 | ) 51 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 52 | list(APPEND CPP_WRAPPER_SOURCES_APP 53 | "flutter_engine.cc" 54 | "flutter_view_controller.cc" 55 | ) 56 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 57 | 58 | # Wrapper sources needed for a plugin. 59 | add_library(flutter_wrapper_plugin STATIC 60 | ${CPP_WRAPPER_SOURCES_CORE} 61 | ${CPP_WRAPPER_SOURCES_PLUGIN} 62 | ) 63 | apply_standard_settings(flutter_wrapper_plugin) 64 | set_target_properties(flutter_wrapper_plugin PROPERTIES 65 | POSITION_INDEPENDENT_CODE ON) 66 | set_target_properties(flutter_wrapper_plugin PROPERTIES 67 | CXX_VISIBILITY_PRESET hidden) 68 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 69 | target_include_directories(flutter_wrapper_plugin PUBLIC 70 | "${WRAPPER_ROOT}/include" 71 | ) 72 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 73 | 74 | # Wrapper sources needed for the runner. 75 | add_library(flutter_wrapper_app STATIC 76 | ${CPP_WRAPPER_SOURCES_CORE} 77 | ${CPP_WRAPPER_SOURCES_APP} 78 | ) 79 | apply_standard_settings(flutter_wrapper_app) 80 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 81 | target_include_directories(flutter_wrapper_app PUBLIC 82 | "${WRAPPER_ROOT}/include" 83 | ) 84 | add_dependencies(flutter_wrapper_app flutter_assemble) 85 | 86 | # === Flutter tool backend === 87 | # _phony_ is a non-existent file to force this command to run every time, 88 | # since currently there's no way to get a full input/output list from the 89 | # flutter tool. 90 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 91 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 92 | add_custom_command( 93 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 94 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 95 | ${CPP_WRAPPER_SOURCES_APP} 96 | ${PHONY_OUTPUT} 97 | COMMAND ${CMAKE_COMMAND} -E env 98 | ${FLUTTER_TOOL_ENVIRONMENT} 99 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 100 | ${FLUTTER_TARGET_PLATFORM} $ 101 | VERBATIM 102 | ) 103 | add_custom_target(flutter_assemble DEPENDS 104 | "${FLUTTER_LIBRARY}" 105 | ${FLUTTER_LIBRARY_HEADERS} 106 | ${CPP_WRAPPER_SOURCES_CORE} 107 | ${CPP_WRAPPER_SOURCES_PLUGIN} 108 | ${CPP_WRAPPER_SOURCES_APP} 109 | ) 110 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | 11 | void RegisterPlugins(flutter::PluginRegistry* registry) { 12 | FileSelectorWindowsRegisterWithRegistrar( 13 | registry->GetRegistrarForPlugin("FileSelectorWindows")); 14 | } 15 | -------------------------------------------------------------------------------- /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 | file_selector_windows 7 | ) 8 | 9 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 10 | ) 11 | 12 | set(PLUGIN_BUNDLED_LIBRARIES) 13 | 14 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 15 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 16 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 19 | endforeach(plugin) 20 | 21 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 22 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 23 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 24 | endforeach(ffi_plugin) 25 | -------------------------------------------------------------------------------- /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/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) 64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0,0 67 | #endif 68 | 69 | #if defined(FLUTTER_VERSION) 70 | #define VERSION_AS_STRING FLUTTER_VERSION 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "org.fossasia" "\0" 93 | VALUE "FileDescription", "magic_epaper_app" "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "magic_epaper_app" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2024 org.fossasia. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "magic_epaper_app.exe" "\0" 98 | VALUE "ProductName", "magic_epaper_app" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /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"magic_epaper_app", 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/fossasia/magic-epaper-app/3bcd2d3913ef2586ba25c58c20505cce150100b1/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 | -------------------------------------------------------------------------------- /windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates a win32 window with |title| that is positioned and sized using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size this function will scale the inputted width and height as 35 | // as appropriate for the default monitor. The window is invisible until 36 | // |Show| is called. Returns true if the window was created successfully. 37 | bool Create(const std::wstring& title, const Point& origin, const Size& size); 38 | 39 | // Show the current window. Returns true if the window was successfully shown. 40 | bool Show(); 41 | 42 | // Release OS resources associated with window. 43 | void Destroy(); 44 | 45 | // Inserts |content| into the window tree. 46 | void SetChildContent(HWND content); 47 | 48 | // Returns the backing Window handle to enable clients to set icon and other 49 | // window properties. Returns nullptr if the window has been destroyed. 50 | HWND GetHandle(); 51 | 52 | // If true, closing this window will quit the application. 53 | void SetQuitOnClose(bool quit_on_close); 54 | 55 | // Return a RECT representing the bounds of the current client area. 56 | RECT GetClientArea(); 57 | 58 | protected: 59 | // Processes and route salient window messages for mouse handling, 60 | // size change and DPI. Delegates handling of these to member overloads that 61 | // inheriting classes can handle. 62 | virtual LRESULT MessageHandler(HWND window, 63 | UINT const message, 64 | WPARAM const wparam, 65 | LPARAM const lparam) noexcept; 66 | 67 | // Called when CreateAndShow is called, allowing subclass window-related 68 | // setup. Subclasses should return false if setup fails. 69 | virtual bool OnCreate(); 70 | 71 | // Called when Destroy is called. 72 | virtual void OnDestroy(); 73 | 74 | private: 75 | friend class WindowClassRegistrar; 76 | 77 | // OS callback called by message pump. Handles the WM_NCCREATE message which 78 | // is passed when the non-client area is being created and enables automatic 79 | // non-client DPI scaling so that the non-client area automatically 80 | // responds to changes in DPI. All other messages are handled by 81 | // MessageHandler. 82 | static LRESULT CALLBACK WndProc(HWND const window, 83 | UINT const message, 84 | WPARAM const wparam, 85 | LPARAM const lparam) noexcept; 86 | 87 | // Retrieves a class instance pointer for |window| 88 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 89 | 90 | // Update the window frame's theme to match the system theme. 91 | static void UpdateTheme(HWND const window); 92 | 93 | bool quit_on_close_ = false; 94 | 95 | // window handle for top level window. 96 | HWND window_handle_ = nullptr; 97 | 98 | // window handle for hosted content. 99 | HWND child_content_ = nullptr; 100 | }; 101 | 102 | #endif // RUNNER_WIN32_WINDOW_H_ 103 | --------------------------------------------------------------------------------