├── .gitattributes ├── .github └── workflows │ └── publish.yml ├── .gitignore ├── .metadata ├── .pubignore ├── .vscode └── launch.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── .idea │ ├── .gitignore │ ├── .name │ ├── caches │ │ └── build_file_checksums.ser │ ├── codeStyles │ │ ├── Project.xml │ │ └── codeStyleConfig.xml │ ├── compiler.xml │ ├── gradle.xml │ ├── jarRepositories.xml │ ├── misc.xml │ ├── modules.xml │ └── vcs.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── com │ └── incrediblezayed │ └── file_saver │ ├── Dialog.kt │ ├── FileSaverPlugin.kt │ └── FileUtils.kt ├── example ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── incrediblezayed │ │ │ │ │ ├── example │ │ │ │ │ └── MainActivity.kt │ │ │ │ │ └── file_saver_example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── main.dart │ ├── save_with_byte_proxy.dart │ └── save_with_file_proxy.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 │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── app_icon_1024.png │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_512.png │ │ │ └── app_icon_64.png │ │ ├── Base.lproj │ │ └── MainMenu.xib │ │ ├── Configs │ │ ├── AppInfo.xcconfig │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── Warnings.xcconfig │ │ ├── DebugProfile.entitlements │ │ ├── Info.plist │ │ ├── MainFlutterWindow.swift │ │ └── Release.entitlements ├── pubspec.lock ├── pubspec.yaml ├── test │ └── widget_test.dart ├── web │ ├── favicon.png │ ├── icons │ │ ├── Icon-192.png │ │ ├── Icon-512.png │ │ ├── Icon-maskable-192.png │ │ └── Icon-maskable-512.png │ ├── index.html │ └── manifest.json └── 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 ├── file_saver.iml ├── images ├── android.png ├── iOSXcode.png ├── ios.png ├── macOSXcode.png └── macos.png ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── Dialog.swift │ ├── FileSaverPlugin.h │ ├── FileSaverPlugin.m │ └── SwiftFileSaverPlugin.swift └── file_saver.podspec ├── lib ├── file_saver.dart ├── file_saver_web.dart └── src │ ├── models │ ├── file.model.dart │ └── link_details.dart │ ├── platform_handler │ ├── platform_handler.dart │ ├── platform_handler_all.dart │ ├── platform_handler_stub.dart │ └── platform_handler_web.dart │ ├── saver.dart │ └── utils │ ├── helpers.dart │ └── mime_types.dart ├── linux ├── CMakeLists.txt ├── file_saver_plugin.cc └── include │ └── file_saver │ └── file_saver_plugin.h ├── macos ├── Classes │ ├── Dialog.swift │ └── FileSaverPlugin.swift └── file_saver.podspec ├── pubspec.lock ├── pubspec.yaml ├── test └── file_saver_test.dart └── windows ├── .gitignore ├── CMakeLists.txt ├── Dialog.cpp ├── file_saver_plugin.cpp ├── file_saver_plugin.h ├── file_saver_plugin_c_api.cpp └── include └── file_saver ├── file_saver_plugin.h └── file_saver_plugin_c_api.h /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to pub.dev 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v[0-9]+.[0-9]+.[0-9]+[0-9]' 7 | 8 | jobs: 9 | add-contributors: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: BobAnkh/add-contributors@master 14 | with: 15 | CONTRIBUTOR: '### Contributors' 16 | COLUMN_PER_ROW: '6' 17 | ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | IMG_WIDTH: '100' 19 | FONT_SIZE: '14' 20 | PATH: '/README.md' 21 | COMMIT_MESSAGE: 'docs(README): update contributors' 22 | AVATAR_SHAPE: 'round' 23 | publish: 24 | permissions: 25 | id-token: write 26 | contents: write 27 | runs-on: ubuntu-latest 28 | 29 | steps: 30 | - name: Checkout repository 31 | uses: actions/checkout@v2 32 | - name: Set up Flutter 33 | uses: subosito/flutter-action@v1 34 | with: 35 | flutter-version: "3.22.0" 36 | - name: Set up Dart 37 | uses: dart-lang/setup-dart@v1 38 | - name: Get dependencies 39 | run: flutter pub get 40 | - name: Publish to pub.dev dry run 41 | run: dart pub publish --dry-run 42 | - name: Publish to pub.dev 43 | run: dart pub publish -f 44 | - name: Extract release notes 45 | id: extract-release-notes 46 | uses: ffurrer2/extract-release-notes@v1 47 | - name: Create Release 48 | uses: actions/create-release@v1 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | with: 52 | tag_name: ${{ github.ref_name }} 53 | release_name: ${{ github.ref_name }} 54 | body: ${{ steps.extract-release-notes.outputs.release_notes }} 55 | draft: false 56 | prerelease: false 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | /.idea/ 9 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: f1875d570e39de09040c8f79aa13cc56baab8db1 8 | channel: stable 9 | 10 | project_type: plugin 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 17 | base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 18 | - platform: android 19 | create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 20 | base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 21 | - platform: ios 22 | create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 23 | base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 24 | - platform: linux 25 | create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 26 | base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 27 | - platform: macos 28 | create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 29 | base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 30 | - platform: web 31 | create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 32 | base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 33 | - platform: windows 34 | create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 35 | base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /.pubignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "file_saver", 9 | "request": "launch", 10 | "type": "dart" 11 | }, 12 | { 13 | "name": "file_saver (profile mode)", 14 | "request": "launch", 15 | "type": "dart", 16 | "flutterMode": "profile" 17 | }, 18 | { 19 | "name": "file_saver (release mode)", 20 | "request": "launch", 21 | "type": "dart", 22 | "flutterMode": "release" 23 | }, 24 | { 25 | "name": "example", 26 | "cwd": "example", 27 | "request": "launch", 28 | "type": "dart" 29 | }, 30 | { 31 | "name": "example (profile mode)", 32 | "cwd": "example", 33 | "request": "launch", 34 | "type": "dart", 35 | "flutterMode": "profile" 36 | }, 37 | { 38 | "name": "example (release mode)", 39 | "cwd": "example", 40 | "request": "launch", 41 | "type": "dart", 42 | "flutterMode": "release" 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, Hassan Ansari 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FileSaver 2 | [![Discord](https://www.hassanansari.dev/public/file_saver_discord.png)](https://discord.gg/4yRFt68kty) 3 | 4 | 5 | ## Huge Shoutout to all the contributors and the people who are using this package, I'm really grateful to all of you. Thank you for your support. 6 | 7 | This plugin package primarily focuses on one task: saving files on Android, iOS, Web, Windows, MacOS, and Linux. 8 | It might not have a plethora of features, but it does this job well. 9 | This package depends on path_provider for Android and iOS and basic html anchor for Web. The main reason I built this plugin was to 10 | avoid using HTML just for downloading files. The plugin is pretty simple and saves the file in Downloads folder in 11 | Windows, MacOS, Linux and directly downloads the file in Web, in iOS, the file is Saved in Application 12 | Documents Directory, and in Android it is saved in the applications files directory Android/data/your.package.name/file/your_file.extension. 13 | 14 | ## Getting Started 15 | 16 | The plugin itself is pretty easy to use. Just call the method saveFile() with respective arguments. 17 | 18 | ```dart 19 | await FileSaver.instance.saveFile({ 20 | required String name, 21 | Uint8List? bytes, 22 | File? file, 23 | String? filePath, 24 | LinkDetails? link, 25 | String ext = "", 26 | MimeType mimeType = MimeType.other, 27 | String? customMimeType, 28 | Dio? dioClient, 29 | Uint8List Function(Uint8List)? transformDioResponse, 30 | }); 31 | ``` 32 | 33 | This saveFile() method has 8 Named arguments. 34 | 35 | _String name_ which takes the name of the file,\ 36 | _Uint8List bytes_ which will be your actual encoded file,\ 37 | Or\ 38 | _File file_ which will be your file in the File object (from dart:io)\ 39 | Or\ 40 | _Stirng filePath_ which will be your file path\ 41 | Or\ 42 | _LinkDetails link_ which will provide the link, header, request methid and body to your file. LinkDetails can be used as 43 | ```dart 44 | LinkDetails( 45 | link: "https://www.example.com/file.extentions", 46 | headers: {"your-header-key": "you-header-value"}, 47 | method: "POST", 48 | body: body 49 | ) 50 | ``` 51 | \ 52 | Out of these parameters, you will have to use atleast one 53 | 54 | _String ext_ this will be your file extension.\ 55 | Another parameter is _MimeType type_ Specifically for Web, which will be your file 56 | type 57 | 58 | _String customMimeType_ this will be your custom mime type, if you want to use your own mime type, you can use this parameter 59 | 60 | _Dio dioClient_ this will be your dio client, if you want to use dio for downloading the file, you can use this parameter 61 | 62 | _Uint8List Function(Uint8List) transformDioResponse_ this will be your function to transform the response, if you want to transform the response as per your requirement, you can use this parameter 63 | 64 | MimeType is also included in my Package, I've included types for **Sheets, Presentation, Word, Plain Text, PDF, 65 | MP3, MP4 and many other common formats** 66 | 67 | or you can call saveAs() _only available for android and iOS & macOS at the moment_ 68 | 69 | ```dart 70 | await FileSaver.instance.saveAs({ 71 | required String name, 72 | Uint8List? bytes, 73 | File? file, 74 | String? filePath, 75 | LinkDetails? link, 76 | required String ext, 77 | required MimeType mimeType, 78 | String? customMimeType, 79 | Dio? dioClient, 80 | Uint8List Function(Uint8List)? transformDioResponse, 81 | }); 82 | ``` 83 | 84 | All the parameters in this method is same as the saveFile() method. 85 | 86 | ### Note: customMimeType can only be used when mimeType is set to MimeType.custom 87 | 88 | ### Storage Permissions & Network Permissions: 89 | 90 | > ##### _These Settings are optional for iOS, as in iOS the file will be saved in application documents directory but will not be visible in Files application, to make your file visible in iOS Files application, make the changes mentioned below._ 91 | 92 | #### iOS: 93 | 94 | Go to your project folder, ios/Runner/info.plist and Add these keys: 95 | 96 | ```xml 97 | LSSupportsOpeningDocumentsInPlace 98 | 99 | UIFileSharingEnabled 100 | 101 | ``` 102 | 103 | ![iOS](https://raw.githubusercontent.com/incrediblezayed/file_saver/main/images/ios.png) 104 | 105 | #### Or in XCode: 106 | 107 | Open Your Project in XCode (Open XCode -> Open a project or file -> Your_Project_Folder/ios/Runner.xcworkspace) 108 | Open info.plist Add these rows: 109 | 110 | Application supports iTunes file sharing (Boolean -> Yes) 111 | 112 | Supports opening documents in place (Boolean -> Yes) 113 | 114 | ![iOS Xcode](https://raw.githubusercontent.com/incrediblezayed/file_saver/main/images/iOSXcode.png) 115 | 116 | #### macOS: 117 | 118 | Go to your project folder, macOS/Runner/DebugProfile.entitlements 119 | 120 | > For release you need to open 'YOUR_PROJECT_NAME'Profile.entitlements 121 | 122 | and add the following key: 123 | 124 | ```xml 125 | com.apple.security.files.downloads.read-write 126 | 127 | ``` 128 | 129 | ![MacOS](https://raw.githubusercontent.com/incrediblezayed/file_saver/main/images/macos.png) 130 | 131 | #### Or in XCode: 132 | 133 | Open Your Project in XCode (Open XCode -> Open a project or file -> Your_Project_Folder/macos/Runner.xcworkspace) 134 | Open your entitlement file (DebugProfile.entitlements & 'YOUR_PROJECT_NAME'Profile.entitlements) 135 | 136 | Add these rows: 137 | ![MacOS Xcode](https://raw.githubusercontent.com/incrediblezayed/file_saver/main/images/macOSXcode.png) 138 | 139 | and if you get Client Socket Exception while saving files in MacOS from link, 140 | you have to add this key in the DebugProfile.entitlements and Release.entitlements of your macOS application and set the value to true 141 | 142 | ```xml 143 | com.apple.security.network.client 144 | 145 | ``` 146 | 147 | *You can find these files in the project_folder/macos/Runner/ directory.* 148 | 149 | #### And You're done 150 | 151 | ## Thank You For Reading this far :) 152 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | linter: 3 | rules: 4 | prefer_single_quotes: true 5 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /android/.idea/.name: -------------------------------------------------------------------------------- 1 | file_saver -------------------------------------------------------------------------------- /android/.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/android/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /android/.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 119 | 120 | 122 | 123 | -------------------------------------------------------------------------------- /android/.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /android/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | -------------------------------------------------------------------------------- /android/.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /android/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /android/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /android/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.incrediblezayed.file_saver' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | ext.kotlin_version = '1.9.10' 6 | ext.coroutinesVersion = '1.6.4' 7 | repositories { 8 | google() 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | classpath 'com.android.tools.build:gradle:8.1.2' 14 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 15 | } 16 | } 17 | 18 | rootProject.allprojects { 19 | repositories { 20 | google() 21 | mavenCentral() 22 | } 23 | } 24 | 25 | apply plugin: 'com.android.library' 26 | apply plugin: 'kotlin-android' 27 | 28 | android { 29 | namespace "com.incrediblezayed.file_saver" 30 | compileSdk 34 31 | 32 | sourceSets { 33 | main.java.srcDirs += 'src/main/kotlin' 34 | } 35 | defaultConfig { 36 | minSdkVersion 19 37 | } 38 | compileOptions { 39 | targetCompatibility JavaVersion.VERSION_1_8 40 | sourceCompatibility JavaVersion.VERSION_1_8 41 | } 42 | 43 | kotlinOptions { 44 | jvmTarget = '1.8' 45 | } 46 | } 47 | 48 | 49 | 50 | dependencies { 51 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion" 52 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion" 53 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 54 | implementation 'androidx.annotation:annotation:1.7.0' 55 | } 56 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.enableR8=true 5 | -------------------------------------------------------------------------------- /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.0-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'file_saver' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/kotlin/com/incrediblezayed/file_saver/Dialog.kt: -------------------------------------------------------------------------------- 1 | package com.incrediblezayed.file_saver 2 | 3 | import android.app.Activity 4 | import android.content.ContentUris 5 | import android.content.Context 6 | import android.content.Intent 7 | import android.database.Cursor 8 | import android.net.Uri 9 | import android.os.Build 10 | import android.os.Environment 11 | import android.provider.DocumentsContract 12 | import android.provider.MediaStore 13 | import android.text.TextUtils 14 | import android.util.Log 15 | import androidx.annotation.RequiresApi 16 | import io.flutter.plugin.common.MethodChannel 17 | import io.flutter.plugin.common.PluginRegistry 18 | import kotlinx.coroutines.CoroutineScope 19 | import kotlinx.coroutines.Dispatchers 20 | import kotlinx.coroutines.launch 21 | import java.io.File 22 | import java.io.OutputStream 23 | 24 | 25 | private const val SAVE_FILE = 886325063 26 | 27 | class Dialog(private val activity: Activity) : PluginRegistry.ActivityResultListener { 28 | private var result: MethodChannel.Result? = null 29 | private var bytes: ByteArray? = null 30 | private var fileName: String? = null 31 | private val TAG = "Dialog Activity" 32 | 33 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { 34 | if (requestCode != SAVE_FILE) { 35 | return false 36 | } 37 | 38 | if (resultCode == Activity.RESULT_OK && data?.data != null) { 39 | Log.d(TAG, "Starting file operation") 40 | completeFileOperation(data.data!!) 41 | } else { 42 | Log.d(TAG, "Activity result was null") 43 | result?.success(null) 44 | result = null 45 | } 46 | 47 | return true 48 | } 49 | 50 | fun openFileManager( 51 | fileName: String?, 52 | ext: String?, 53 | bytes: ByteArray?, 54 | type: String?, 55 | result: MethodChannel.Result 56 | ) { 57 | Log.d(TAG, "Opening File Manager") 58 | this.result = result 59 | this.bytes = bytes 60 | this.fileName = fileName 61 | val intent = 62 | Intent(Intent.ACTION_CREATE_DOCUMENT) 63 | intent.addCategory(Intent.CATEGORY_OPENABLE) 64 | intent.putExtra(Intent.EXTRA_TITLE, "$fileName.$ext") 65 | intent.putExtra( 66 | DocumentsContract.EXTRA_INITIAL_URI, 67 | Environment.getExternalStorageDirectory().path 68 | ) 69 | intent.type = type 70 | activity.startActivityForResult(intent, SAVE_FILE) 71 | } 72 | 73 | private fun completeFileOperation(uri: Uri) { 74 | CoroutineScope(Dispatchers.Main).launch { 75 | try { 76 | saveFile(uri) 77 | val fileUtils = FileUtils(activity) 78 | result?.success(fileUtils.getPath(uri)); 79 | result = null 80 | //result?.success(getRealPathFromUri(activity, uri)) 81 | } catch (e: SecurityException) { 82 | Log.d(TAG, "Security Exception while saving file" + e.message) 83 | 84 | result?.error("Security Exception", e.localizedMessage, e) 85 | result = null 86 | } catch (e: Exception) { 87 | Log.d(TAG, "Exception while saving file" + e.message) 88 | result?.error("Error", e.localizedMessage, e) 89 | result = null 90 | } 91 | } 92 | } 93 | 94 | private fun saveFile(uri: Uri) { 95 | try { 96 | Log.d(TAG, "Saving file") 97 | 98 | val opStream = activity.contentResolver.openOutputStream(uri) 99 | opStream?.write(bytes) 100 | 101 | } catch (e: Exception) { 102 | Log.d(TAG, "Error while writing file" + e.message) 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/com/incrediblezayed/file_saver/FileSaverPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.incrediblezayed.file_saver 2 | 3 | 4 | import android.util.Log 5 | import androidx.annotation.NonNull 6 | import androidx.core.app.ActivityCompat 7 | import io.flutter.embedding.engine.plugins.FlutterPlugin 8 | import io.flutter.embedding.engine.plugins.activity.ActivityAware 9 | import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding 10 | import io.flutter.plugin.common.MethodCall 11 | import io.flutter.plugin.common.MethodChannel 12 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler 13 | import io.flutter.plugin.common.MethodChannel.Result 14 | import java.io.File 15 | import java.lang.Exception 16 | import java.util.jar.Manifest 17 | 18 | 19 | /** FileSaverPlugin */ 20 | class FileSaverPlugin : FlutterPlugin, ActivityAware, MethodCallHandler { 21 | private var dialog: Dialog? = null 22 | private var activity: ActivityPluginBinding? = null 23 | private var pluginBinding: FlutterPlugin.FlutterPluginBinding? = null 24 | private var methodChannel: MethodChannel? = null 25 | private var result: Result? = null 26 | private val tag: String = "FileSaver" 27 | override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { 28 | if (pluginBinding != null) { 29 | Log.d(tag, "Already Initialized") 30 | } 31 | pluginBinding = flutterPluginBinding 32 | val messenger = pluginBinding!!.binaryMessenger 33 | methodChannel = MethodChannel(messenger, "file_saver") 34 | methodChannel?.setMethodCallHandler(this) 35 | } 36 | 37 | override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { 38 | Log.d(tag, "Detached From Engine") 39 | methodChannel = null 40 | pluginBinding = null 41 | if (dialog != null) { 42 | activity?.removeActivityResultListener(dialog!!) 43 | dialog = null 44 | } 45 | methodChannel?.setMethodCallHandler(null) 46 | } 47 | 48 | 49 | override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { 50 | if (dialog == null) { 51 | Log.d(tag, "Dialog was null") 52 | createFileDialog() 53 | } 54 | try { 55 | this.result = result 56 | when (call.method) { 57 | "saveFile" -> { 58 | Log.d(tag, "Get directory Method Called") 59 | val dir: String = saveFile( 60 | fileName = call.argument("name"), 61 | bytes = call.argument("bytes"), 62 | extension = call.argument("ext") 63 | ) 64 | result.success(dir) 65 | } 66 | 67 | "saveAs" -> { 68 | Log.d(tag, "Save as Method Called") 69 | dialog!!.openFileManager( 70 | fileName = call.argument("name"), 71 | ext = call.argument("ext"), 72 | bytes = call.argument("bytes"), 73 | type = call.argument("mimeType"), 74 | result = result 75 | ) 76 | 77 | } 78 | 79 | else -> { 80 | Log.d(tag, "Unknown Method called " + call.method!!) 81 | result.notImplemented() 82 | } 83 | } 84 | } catch (e: Exception) { 85 | Log.d(tag, "Error While Calling method" + e.message) 86 | } 87 | 88 | } 89 | 90 | private fun saveFile(fileName: String?, bytes: ByteArray?, extension: String?): String { 91 | return try { 92 | val uri = activity!!.activity.baseContext.getExternalFilesDir(null) 93 | val file = File(uri!!.absolutePath + "/" + fileName + extension) 94 | file.writeBytes(bytes!!) 95 | uri.absolutePath + "/" + file.name 96 | } catch (e: Exception) { 97 | Log.d(tag, "Error While Saving File" + e.message) 98 | "Error While Saving File" + e.message 99 | } 100 | } 101 | 102 | override fun onDetachedFromActivity() { 103 | Log.d(tag, "Detached From Activity") 104 | if (dialog != null) { 105 | activity?.removeActivityResultListener(dialog!!) 106 | dialog = null 107 | } 108 | activity = null 109 | } 110 | 111 | override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { 112 | Log.d(tag, "Re Attached to Activity") 113 | this.activity = binding 114 | } 115 | 116 | override fun onAttachedToActivity(binding: ActivityPluginBinding) { 117 | Log.d(tag, "Attached to Activity") 118 | this.activity = binding 119 | } 120 | 121 | override fun onDetachedFromActivityForConfigChanges() { 122 | Log.d(tag, "On Detached From ConfigChanges") 123 | if (dialog != null) { 124 | activity?.removeActivityResultListener(dialog!!) 125 | dialog = null 126 | } 127 | activity = null 128 | } 129 | 130 | private fun createFileDialog(): Boolean { 131 | Log.d(tag, "Creating File Dialog Activity") 132 | var dialog: Dialog? = null 133 | if (activity != null) { 134 | dialog = Dialog( 135 | activity = activity!!.activity 136 | ) 137 | activity!!.addActivityResultListener(dialog) 138 | } else { 139 | Log.d(tag, "Activity was null") 140 | if (result != null) result?.error("NullActivity", "Activity was Null", null) 141 | } 142 | this.dialog = dialog 143 | return dialog != null 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Android Studio will place build artifacts here 43 | /android/app/debug 44 | /android/app/profile 45 | /android/app/release 46 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 00ee2e59e7f613cdc4e8357f73db356581bef58b 8 | channel: master 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # file_saver_example 2 | 3 | Demonstrates how to use the file_saver plugin. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | linter: 3 | rules: 4 | 5 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new FileNotFoundException('Flutter SDK not found. Define location with flutter.sdk in the local.properties file.') 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 34 30 | namespace "com.incrediblezayed.file_saver_example" 31 | 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_11 34 | targetCompatibility JavaVersion.VERSION_11 35 | } 36 | 37 | kotlinOptions { 38 | jvmTarget = '11' 39 | } 40 | 41 | sourceSets { 42 | main.java.srcDirs += 'src/main/kotlin' 43 | } 44 | 45 | defaultConfig { 46 | applicationId 'com.incrediblezayed.file_saver_example' 47 | minSdkVersion 19 48 | targetSdkVersion 34 49 | versionCode flutterVersionCode.toInteger() 50 | versionName flutterVersionName 51 | } 52 | 53 | buildTypes { 54 | release { 55 | // TODO: Add your own signing config for the release build. 56 | // Signing with the debug keys for now, so `flutter run --release` works. 57 | signingConfig signingConfigs.debug 58 | } 59 | } 60 | } 61 | 62 | flutter { 63 | source '../..' 64 | } 65 | 66 | dependencies { 67 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 68 | } 69 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 18 | 22 | 24 | 29 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 40 | 41 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/incrediblezayed/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.incrediblezayed.example 2 | 3 | import android.os.Build 4 | import android.os.Bundle 5 | import android.os.PersistableBundle 6 | import androidx.annotation.RequiresApi 7 | import androidx.core.app.ActivityCompat 8 | import io.flutter.embedding.android.FlutterActivity 9 | import io.flutter.embedding.engine.FlutterEngine 10 | 11 | class MainActivity: FlutterActivity() { 12 | @RequiresApi(Build.VERSION_CODES.M) 13 | override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) { 14 | super.onCreate(savedInstanceState, persistentState) 15 | ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE), 1) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/incrediblezayed/file_saver_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.incrediblezayed.file_saver_example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.8.0' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:8.1.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | tasks.register("clean", Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/ephemeral/ 22 | Flutter/app.flx 23 | Flutter/app.zip 24 | Flutter/flutter_assets/ 25 | Flutter/flutter_export_environment.sh 26 | ServiceDefinitions.json 27 | Runner/GeneratedPluginRegistrant.* 28 | 29 | # Exceptions to above rules. 30 | !default.mode1v3 31 | !default.mode2v3 32 | !default.pbxuser 33 | !default.perspectivev3 34 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '11.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - file_saver (0.0.1): 3 | - Flutter 4 | - Flutter (1.0.0) 5 | - path_provider_foundation (0.0.1): 6 | - Flutter 7 | - FlutterMacOS 8 | - permission_handler_apple (9.0.4): 9 | - Flutter 10 | 11 | DEPENDENCIES: 12 | - file_saver (from `.symlinks/plugins/file_saver/ios`) 13 | - Flutter (from `Flutter`) 14 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`) 15 | - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) 16 | 17 | EXTERNAL SOURCES: 18 | file_saver: 19 | :path: ".symlinks/plugins/file_saver/ios" 20 | Flutter: 21 | :path: Flutter 22 | path_provider_foundation: 23 | :path: ".symlinks/plugins/path_provider_foundation/ios" 24 | permission_handler_apple: 25 | :path: ".symlinks/plugins/permission_handler_apple/ios" 26 | 27 | SPEC CHECKSUMS: 28 | file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808 29 | Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 30 | path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9 31 | permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce 32 | 33 | PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 34 | 35 | COCOAPODS: 1.12.1 36 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | file_saver_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | LSSupportsOpeningDocumentsInPlace 49 | 50 | UIFileSharingEnabled 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:excel/excel.dart'; 2 | import 'package:file_saver/file_saver.dart'; 3 | import 'package:file_saver_example/save_with_byte_proxy.dart'; 4 | import 'package:file_saver_example/save_with_file_proxy.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | void main() { 8 | runApp(const MyApp()); 9 | } 10 | 11 | class MyApp extends StatefulWidget { 12 | const MyApp({Key? key}) : super(key: key); 13 | @override 14 | State createState() => _MyAppState(); 15 | } 16 | 17 | class _MyAppState extends State { 18 | TextEditingController textEditingController = TextEditingController(); 19 | TextEditingController linkController = TextEditingController( 20 | text: 21 | "https://i.pinimg.com/564x/80/d4/90/80d490f65d5e6132b2a6e3b5883785f3.jpg"); 22 | TextEditingController extController = TextEditingController(text: "jpg"); 23 | 24 | @override 25 | void initState() { 26 | super.initState(); 27 | } 28 | 29 | List? getExcel() { 30 | final Excel excel = Excel.createExcel(); 31 | final Sheet sheetObject = excel['Sheet1']; 32 | sheetObject.insertColumn(0); 33 | for (int i = 1; i < 10; i++) { 34 | sheetObject.appendRow([TextCellValue(i.toString())]); 35 | } 36 | List? sheets = excel.encode(); 37 | return sheets; 38 | } 39 | 40 | MimeType type = MimeType.jpeg; 41 | 42 | List modes = ['Byte proxy', 'File proxy']; 43 | 44 | int currentMode = 0; 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | final Widget widgetToDisplay; 49 | 50 | if (currentMode == 0) { 51 | widgetToDisplay = const SaveWithByteProxy(); 52 | } else { 53 | widgetToDisplay = const SaveWithFileProxy(); 54 | } 55 | return MaterialApp( 56 | home: Scaffold( 57 | appBar: AppBar( 58 | title: const Text('File Saver'), 59 | actions: [ 60 | DropdownButton( 61 | value: currentMode, 62 | underline: Container(), 63 | items: modes 64 | .asMap() 65 | .entries 66 | .map((entry) => DropdownMenuItem( 67 | value: entry.key, 68 | child: Text(entry.value), 69 | )) 70 | .toList(), 71 | onChanged: (value) { 72 | if (value == null) return; 73 | setState(() { 74 | currentMode = value; 75 | }); 76 | }, 77 | ) 78 | ], 79 | ), 80 | body: widgetToDisplay, 81 | ), 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /example/lib/save_with_file_proxy.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | import 'dart:io'; 3 | 4 | import 'package:file_picker/file_picker.dart'; 5 | import 'package:file_saver/file_saver.dart'; 6 | import 'package:flutter/foundation.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:permission_handler/permission_handler.dart'; 9 | 10 | class SaveWithFileProxy extends StatefulWidget { 11 | const SaveWithFileProxy({Key? key}) : super(key: key); 12 | 13 | @override 14 | State createState() => _SaveWithFileProxyState(); 15 | } 16 | 17 | class _SaveWithFileProxyState extends State { 18 | TextEditingController nameController = TextEditingController(); 19 | TextEditingController originalFileController = TextEditingController(); 20 | TextEditingController destinationFileController = TextEditingController(); 21 | TextEditingController extController = TextEditingController(); 22 | 23 | MimeType type = MimeType.jpeg; 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Scaffold( 28 | body: Padding( 29 | padding: const EdgeInsets.all(8.0), 30 | child: Column( 31 | children: [ 32 | Center( 33 | child: Padding( 34 | padding: const EdgeInsets.all(8.0), 35 | child: TextField( 36 | controller: nameController, 37 | decoration: const InputDecoration( 38 | labelText: "Name", 39 | hintText: "Something", 40 | border: OutlineInputBorder(), 41 | ), 42 | ), 43 | ), 44 | ), 45 | Center( 46 | child: Padding( 47 | padding: const EdgeInsets.all(8.0), 48 | child: TextField( 49 | controller: originalFileController, 50 | decoration: InputDecoration( 51 | labelText: "Original file", 52 | border: const OutlineInputBorder(), 53 | suffixIcon: IconButton( 54 | onPressed: () async { 55 | FilePickerResult? result = 56 | await FilePicker.platform.pickFiles(); 57 | 58 | final resultPath = result?.files.single.path; 59 | 60 | if (resultPath != null) { 61 | File file = File(resultPath); 62 | originalFileController.text = file.path; 63 | } else { 64 | // User canceled the picker 65 | } 66 | }, 67 | icon: const Icon(Icons.folder), 68 | ), 69 | ), 70 | ), 71 | ), 72 | ), 73 | Center( 74 | child: Padding( 75 | padding: const EdgeInsets.all(8.0), 76 | child: TextField( 77 | controller: extController, 78 | decoration: const InputDecoration( 79 | labelText: "Extension", 80 | hintText: "jpg", 81 | border: OutlineInputBorder()), 82 | ), 83 | ), 84 | ), 85 | DropdownButton( 86 | value: type, 87 | items: MimeType.values 88 | .map((e) => DropdownMenuItem(value: e, child: Text(e.name))) 89 | .toList(), 90 | onChanged: (value) { 91 | setState(() { 92 | type = value as MimeType; 93 | }); 94 | }, 95 | ), 96 | Padding( 97 | padding: const EdgeInsets.all(8), 98 | child: ElevatedButton( 99 | onPressed: () async { 100 | if (!kIsWeb) { 101 | if (Platform.isIOS || Platform.isAndroid) { 102 | bool status = await Permission.storage.isGranted; 103 | 104 | if (!status) await Permission.storage.request(); 105 | } 106 | } 107 | if (type != MimeType.other && extController.text.isEmpty && context.mounted) { 108 | ScaffoldMessenger.of(context).showSnackBar( 109 | const SnackBar(content: Text("Extension is required"))); 110 | } 111 | 112 | String path = await FileSaver.instance.saveFile( 113 | name: nameController.text == "" 114 | ? "File" 115 | : nameController.text, 116 | //link: linkController.text, 117 | // bytes: Uint8List.fromList(excel.encode()!), 118 | file: File(originalFileController.text), 119 | ext: extController.text, 120 | 121 | ///extController.text, 122 | mimeType: MimeType.microsoftExcel, 123 | ); 124 | log(path); 125 | }, 126 | child: const Text('Save file'), 127 | ), 128 | ) 129 | ], 130 | ), 131 | ), 132 | ); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /example/linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /example/linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(runner LANGUAGES CXX) 3 | 4 | set(BINARY_NAME "file_saver_example") 5 | set(APPLICATION_ID "com.one.file_saver") 6 | 7 | cmake_policy(SET CMP0063 NEW) 8 | 9 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 10 | 11 | # Root filesystem for cross-building. 12 | if(FLUTTER_TARGET_PLATFORM_SYSROOT) 13 | set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) 14 | set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) 15 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 16 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) 17 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 18 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 19 | endif() 20 | 21 | # Configure build options. 22 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 23 | set(CMAKE_BUILD_TYPE "Debug" CACHE 24 | STRING "Flutter build mode" FORCE) 25 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 26 | "Debug" "Profile" "Release") 27 | endif() 28 | 29 | # Compilation settings that should be applied to most targets. 30 | function(APPLY_STANDARD_SETTINGS TARGET) 31 | target_compile_features(${TARGET} PUBLIC cxx_std_14) 32 | target_compile_options(${TARGET} PRIVATE -Wall -Werror) 33 | target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") 34 | target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") 35 | endfunction() 36 | 37 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 38 | 39 | # Flutter library and tool build rules. 40 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 41 | 42 | # System-level dependencies. 43 | find_package(PkgConfig REQUIRED) 44 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 45 | 46 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") 47 | 48 | # Application build 49 | add_executable(${BINARY_NAME} 50 | "main.cc" 51 | "my_application.cc" 52 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 53 | ) 54 | apply_standard_settings(${BINARY_NAME}) 55 | target_link_libraries(${BINARY_NAME} PRIVATE flutter) 56 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) 57 | add_dependencies(${BINARY_NAME} flutter_assemble) 58 | # Only the install-generated bundle's copy of the executable will launch 59 | # correctly, since the resources must in the right relative locations. To avoid 60 | # people trying to run the unbundled copy, put it in a subdirectory instead of 61 | # the default top-level location. 62 | set_target_properties(${BINARY_NAME} 63 | PROPERTIES 64 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" 65 | ) 66 | 67 | # Generated plugin build rules, which manage building the plugins and adding 68 | # them to the application. 69 | include(flutter/generated_plugins.cmake) 70 | 71 | 72 | # === Installation === 73 | # By default, "installing" just makes a relocatable bundle in the build 74 | # directory. 75 | set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") 76 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 77 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 78 | endif() 79 | 80 | # Start with a clean build bundle directory every time. 81 | install(CODE " 82 | file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") 83 | " COMPONENT Runtime) 84 | 85 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 86 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") 87 | 88 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 89 | COMPONENT Runtime) 90 | 91 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 92 | COMPONENT Runtime) 93 | 94 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 95 | COMPONENT Runtime) 96 | 97 | if(PLUGIN_BUNDLED_LIBRARIES) 98 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 99 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 100 | COMPONENT Runtime) 101 | endif() 102 | 103 | # Fully re-copy the assets directory on each build to avoid having stale files 104 | # from a previous install. 105 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 106 | install(CODE " 107 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 108 | " COMPONENT Runtime) 109 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 110 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 111 | 112 | # Install the AOT library on non-Debug builds only. 113 | if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") 114 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 115 | COMPONENT Runtime) 116 | endif() 117 | -------------------------------------------------------------------------------- /example/linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 4 | 5 | # Configuration provided via flutter tool. 6 | include(${EPHEMERAL_DIR}/generated_config.cmake) 7 | 8 | # TODO: Move the rest of this into files in ephemeral. See 9 | # https://github.com/flutter/flutter/issues/57146. 10 | 11 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 12 | # which isn't available in 3.10. 13 | function(list_prepend LIST_NAME PREFIX) 14 | set(NEW_LIST "") 15 | foreach(element ${${LIST_NAME}}) 16 | list(APPEND NEW_LIST "${PREFIX}${element}") 17 | endforeach(element) 18 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 19 | endfunction() 20 | 21 | # === Flutter Library === 22 | # System-level dependencies. 23 | find_package(PkgConfig REQUIRED) 24 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 25 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 26 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 27 | 28 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 29 | 30 | # Published to parent scope for install step. 31 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 32 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 33 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 34 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 35 | 36 | list(APPEND FLUTTER_LIBRARY_HEADERS 37 | "fl_basic_message_channel.h" 38 | "fl_binary_codec.h" 39 | "fl_binary_messenger.h" 40 | "fl_dart_project.h" 41 | "fl_engine.h" 42 | "fl_json_message_codec.h" 43 | "fl_json_method_codec.h" 44 | "fl_message_codec.h" 45 | "fl_method_call.h" 46 | "fl_method_channel.h" 47 | "fl_method_codec.h" 48 | "fl_method_response.h" 49 | "fl_plugin_registrar.h" 50 | "fl_plugin_registry.h" 51 | "fl_standard_message_codec.h" 52 | "fl_standard_method_codec.h" 53 | "fl_string_codec.h" 54 | "fl_value.h" 55 | "fl_view.h" 56 | "flutter_linux.h" 57 | ) 58 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 59 | add_library(flutter INTERFACE) 60 | target_include_directories(flutter INTERFACE 61 | "${EPHEMERAL_DIR}" 62 | ) 63 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 64 | target_link_libraries(flutter INTERFACE 65 | PkgConfig::GTK 66 | PkgConfig::GLIB 67 | PkgConfig::GIO 68 | ) 69 | add_dependencies(flutter flutter_assemble) 70 | 71 | # === Flutter tool backend === 72 | # _phony_ is a non-existent file to force this command to run every time, 73 | # since currently there's no way to get a full input/output list from the 74 | # flutter tool. 75 | add_custom_command( 76 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 77 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 78 | COMMAND ${CMAKE_COMMAND} -E env 79 | ${FLUTTER_TOOL_ENVIRONMENT} 80 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 81 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 82 | VERBATIM 83 | ) 84 | add_custom_target(flutter_assemble DEPENDS 85 | "${FLUTTER_LIBRARY}" 86 | ${FLUTTER_LIBRARY_HEADERS} 87 | ) 88 | -------------------------------------------------------------------------------- /example/linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | 11 | void fl_register_plugins(FlPluginRegistry* registry) { 12 | g_autoptr(FlPluginRegistrar) file_saver_registrar = 13 | fl_plugin_registry_get_registrar_for_plugin(registry, "FileSaverPlugin"); 14 | file_saver_plugin_register_with_registrar(file_saver_registrar); 15 | } 16 | -------------------------------------------------------------------------------- /example/linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /example/linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | file_saver 7 | ) 8 | 9 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 10 | ) 11 | 12 | set(PLUGIN_BUNDLED_LIBRARIES) 13 | 14 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 15 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 16 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 19 | endforeach(plugin) 20 | 21 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 22 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 23 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 24 | endforeach(ffi_plugin) 25 | -------------------------------------------------------------------------------- /example/linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /example/linux/my_application.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | #include 4 | #ifdef GDK_WINDOWING_X11 5 | #include 6 | #endif 7 | 8 | #include "flutter/generated_plugin_registrant.h" 9 | 10 | struct _MyApplication { 11 | GtkApplication parent_instance; 12 | char** dart_entrypoint_arguments; 13 | }; 14 | 15 | G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) 16 | 17 | // Implements GApplication::activate. 18 | static void my_application_activate(GApplication* application) { 19 | MyApplication* self = MY_APPLICATION(application); 20 | GtkWindow* window = 21 | GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); 22 | 23 | // Use a header bar when running in GNOME as this is the common style used 24 | // by applications and is the setup most users will be using (e.g. Ubuntu 25 | // desktop). 26 | // If running on X and not using GNOME then just use a traditional title bar 27 | // in case the window manager does more exotic layout, e.g. tiling. 28 | // If running on Wayland assume the header bar will work (may need changing 29 | // if future cases occur). 30 | gboolean use_header_bar = TRUE; 31 | #ifdef GDK_WINDOWING_X11 32 | GdkScreen* screen = gtk_window_get_screen(window); 33 | if (GDK_IS_X11_SCREEN(screen)) { 34 | const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); 35 | if (g_strcmp0(wm_name, "GNOME Shell") != 0) { 36 | use_header_bar = FALSE; 37 | } 38 | } 39 | #endif 40 | if (use_header_bar) { 41 | GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); 42 | gtk_widget_show(GTK_WIDGET(header_bar)); 43 | gtk_header_bar_set_title(header_bar, "file_saver_example"); 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, "file_saver_example"); 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 GObject::dispose. 85 | static void my_application_dispose(GObject* object) { 86 | MyApplication* self = MY_APPLICATION(object); 87 | g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); 88 | G_OBJECT_CLASS(my_application_parent_class)->dispose(object); 89 | } 90 | 91 | static void my_application_class_init(MyApplicationClass* klass) { 92 | G_APPLICATION_CLASS(klass)->activate = my_application_activate; 93 | G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; 94 | G_OBJECT_CLASS(klass)->dispose = my_application_dispose; 95 | } 96 | 97 | static void my_application_init(MyApplication* self) {} 98 | 99 | MyApplication* my_application_new() { 100 | return MY_APPLICATION(g_object_new(my_application_get_type(), 101 | "application-id", APPLICATION_ID, 102 | "flags", G_APPLICATION_NON_UNIQUE, 103 | nullptr)); 104 | } 105 | -------------------------------------------------------------------------------- /example/linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /example/macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/xcuserdata/ 7 | -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import file_saver 9 | import path_provider_foundation 10 | 11 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 12 | FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin")) 13 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 14 | } 15 | -------------------------------------------------------------------------------- /example/macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.14' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | end 35 | 36 | post_install do |installer| 37 | installer.pods_project.targets.each do |target| 38 | flutter_additional_macos_build_settings(target) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /example/macos/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - file_saver (0.0.1): 3 | - FlutterMacOS 4 | - FlutterMacOS (1.0.0) 5 | - path_provider_foundation (0.0.1): 6 | - Flutter 7 | - FlutterMacOS 8 | 9 | DEPENDENCIES: 10 | - file_saver (from `Flutter/ephemeral/.symlinks/plugins/file_saver/macos`) 11 | - FlutterMacOS (from `Flutter/ephemeral`) 12 | - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) 13 | 14 | EXTERNAL SOURCES: 15 | file_saver: 16 | :path: Flutter/ephemeral/.symlinks/plugins/file_saver/macos 17 | FlutterMacOS: 18 | :path: Flutter/ephemeral 19 | path_provider_foundation: 20 | :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin 21 | 22 | SPEC CHECKSUMS: 23 | file_saver: 44e6fbf666677faf097302460e214e977fdd977b 24 | FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 25 | path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 26 | 27 | PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 28 | 29 | COCOAPODS: 1.12.1 30 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 64 | 65 | 71 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /example/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 = file_saver_example 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.incrediblezayed.fileSaverExample 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2021 com.incrediblezayed. All rights reserved. 15 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.assets.movies.read-write 8 | 9 | com.apple.security.assets.music.read-write 10 | 11 | com.apple.security.assets.pictures.read-write 12 | 13 | com.apple.security.files.downloads.read-write 14 | 15 | com.apple.security.files.user-selected.read-write 16 | 17 | com.apple.security.network.server 18 | 19 | com.apple.security.network.client 20 | 21 | com.apple.security.cs.allow-jit 22 | 23 | 24 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController.init() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: file_saver_example 2 | description: Demonstrates how to use the file_saver plugin. 3 | 4 | publish_to: "none" 5 | 6 | environment: 7 | sdk: ">=2.12.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | excel: 13 | file_picker: ^5.5.0 14 | file_saver: 15 | path: ../ 16 | cupertino_icons: ^1.0.8 17 | permission_handler: 18 | 19 | dev_dependencies: 20 | flutter_test: 21 | sdk: flutter 22 | flutter_lints: ^4.0.0 23 | 24 | flutter: 25 | uses-material-design: true 26 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:file_saver_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Verify Platform version', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(const MyApp()); 17 | 18 | // Verify that platform version is retrieved. 19 | expect( 20 | find.byWidgetPredicate( 21 | (Widget widget) => 22 | widget is Text && widget.data!.startsWith('Running on:'), 23 | ), 24 | findsOneWidget, 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/web/favicon.png -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | file_saver_example 30 | 31 | 32 | 33 | 36 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "file_saver_example", 3 | "short_name": "file_saver_example", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "Demonstrates how to use the file_saver plugin.", 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 | -------------------------------------------------------------------------------- /example/windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /example/windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(file_saver_example LANGUAGES CXX) 3 | 4 | set(BINARY_NAME "file_saver_example") 5 | 6 | cmake_policy(SET CMP0063 NEW) 7 | 8 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 9 | 10 | # Configure build options. 11 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 12 | if(IS_MULTICONFIG) 13 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 14 | CACHE STRING "" FORCE) 15 | else() 16 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 17 | set(CMAKE_BUILD_TYPE "Debug" CACHE 18 | STRING "Flutter build mode" FORCE) 19 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 20 | "Debug" "Profile" "Release") 21 | endif() 22 | endif() 23 | 24 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 25 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 26 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 27 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 28 | 29 | # Use Unicode for all projects. 30 | add_definitions(-DUNICODE -D_UNICODE) 31 | 32 | # Compilation settings that should be applied to most targets. 33 | function(APPLY_STANDARD_SETTINGS TARGET) 34 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 35 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 36 | target_compile_options(${TARGET} PRIVATE /EHsc) 37 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 38 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 39 | endfunction() 40 | 41 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 42 | 43 | # Flutter library and tool build rules. 44 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 45 | 46 | # Application build 47 | add_subdirectory("runner") 48 | 49 | # Generated plugin build rules, which manage building the plugins and adding 50 | # them to the application. 51 | include(flutter/generated_plugins.cmake) 52 | 53 | 54 | # === Installation === 55 | # Support files are copied into place next to the executable, so that it can 56 | # run in place. This is done instead of making a separate bundle (as on Linux) 57 | # so that building and running from within Visual Studio will work. 58 | set(BUILD_BUNDLE_DIR "$") 59 | # Make the "install" step default, as it's required to run. 60 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 61 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 62 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 63 | endif() 64 | 65 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 66 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 67 | 68 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 69 | COMPONENT Runtime) 70 | 71 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 72 | COMPONENT Runtime) 73 | 74 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 75 | COMPONENT Runtime) 76 | 77 | if(PLUGIN_BUNDLED_LIBRARIES) 78 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 79 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 80 | COMPONENT Runtime) 81 | endif() 82 | 83 | # Fully re-copy the assets directory on each build to avoid having stale files 84 | # from a previous install. 85 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 86 | install(CODE " 87 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 88 | " COMPONENT Runtime) 89 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 90 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 91 | 92 | # Install the AOT library on non-Debug builds only. 93 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 94 | CONFIGURATIONS Profile;Release 95 | COMPONENT Runtime) 96 | -------------------------------------------------------------------------------- /example/windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 4 | 5 | # Configuration provided via flutter tool. 6 | include(${EPHEMERAL_DIR}/generated_config.cmake) 7 | 8 | # TODO: Move the rest of this into files in ephemeral. See 9 | # https://github.com/flutter/flutter/issues/57146. 10 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 11 | 12 | # === Flutter Library === 13 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 14 | 15 | # Published to parent scope for install step. 16 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 17 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 18 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 19 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 20 | 21 | list(APPEND FLUTTER_LIBRARY_HEADERS 22 | "flutter_export.h" 23 | "flutter_windows.h" 24 | "flutter_messenger.h" 25 | "flutter_plugin_registrar.h" 26 | "flutter_texture_registrar.h" 27 | ) 28 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 29 | add_library(flutter INTERFACE) 30 | target_include_directories(flutter INTERFACE 31 | "${EPHEMERAL_DIR}" 32 | ) 33 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 34 | add_dependencies(flutter flutter_assemble) 35 | 36 | # === Wrapper === 37 | list(APPEND CPP_WRAPPER_SOURCES_CORE 38 | "core_implementations.cc" 39 | "standard_codec.cc" 40 | ) 41 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 42 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 43 | "plugin_registrar.cc" 44 | ) 45 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 46 | list(APPEND CPP_WRAPPER_SOURCES_APP 47 | "flutter_engine.cc" 48 | "flutter_view_controller.cc" 49 | ) 50 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 51 | 52 | # Wrapper sources needed for a plugin. 53 | add_library(flutter_wrapper_plugin STATIC 54 | ${CPP_WRAPPER_SOURCES_CORE} 55 | ${CPP_WRAPPER_SOURCES_PLUGIN} 56 | ) 57 | apply_standard_settings(flutter_wrapper_plugin) 58 | set_target_properties(flutter_wrapper_plugin PROPERTIES 59 | POSITION_INDEPENDENT_CODE ON) 60 | set_target_properties(flutter_wrapper_plugin PROPERTIES 61 | CXX_VISIBILITY_PRESET hidden) 62 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 63 | target_include_directories(flutter_wrapper_plugin PUBLIC 64 | "${WRAPPER_ROOT}/include" 65 | ) 66 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 67 | 68 | # Wrapper sources needed for the runner. 69 | add_library(flutter_wrapper_app STATIC 70 | ${CPP_WRAPPER_SOURCES_CORE} 71 | ${CPP_WRAPPER_SOURCES_APP} 72 | ) 73 | apply_standard_settings(flutter_wrapper_app) 74 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 75 | target_include_directories(flutter_wrapper_app PUBLIC 76 | "${WRAPPER_ROOT}/include" 77 | ) 78 | add_dependencies(flutter_wrapper_app flutter_assemble) 79 | 80 | # === Flutter tool backend === 81 | # _phony_ is a non-existent file to force this command to run every time, 82 | # since currently there's no way to get a full input/output list from the 83 | # flutter tool. 84 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 85 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 86 | add_custom_command( 87 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 88 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 89 | ${CPP_WRAPPER_SOURCES_APP} 90 | ${PHONY_OUTPUT} 91 | COMMAND ${CMAKE_COMMAND} -E env 92 | ${FLUTTER_TOOL_ENVIRONMENT} 93 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 94 | windows-x64 $ 95 | VERBATIM 96 | ) 97 | add_custom_target(flutter_assemble DEPENDS 98 | "${FLUTTER_LIBRARY}" 99 | ${FLUTTER_LIBRARY_HEADERS} 100 | ${CPP_WRAPPER_SOURCES_CORE} 101 | ${CPP_WRAPPER_SOURCES_PLUGIN} 102 | ${CPP_WRAPPER_SOURCES_APP} 103 | ) 104 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | 12 | void RegisterPlugins(flutter::PluginRegistry* registry) { 13 | FileSaverPluginRegisterWithRegistrar( 14 | registry->GetRegistrarForPlugin("FileSaverPlugin")); 15 | PermissionHandlerWindowsPluginRegisterWithRegistrar( 16 | registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); 17 | } 18 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | file_saver 7 | permission_handler_windows 8 | ) 9 | 10 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 11 | ) 12 | 13 | set(PLUGIN_BUNDLED_LIBRARIES) 14 | 15 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 16 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 17 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 18 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 20 | endforeach(plugin) 21 | 22 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 23 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 24 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 25 | endforeach(ffi_plugin) 26 | -------------------------------------------------------------------------------- /example/windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(runner LANGUAGES CXX) 3 | 4 | add_executable(${BINARY_NAME} WIN32 5 | "flutter_window.cpp" 6 | "main.cpp" 7 | "utils.cpp" 8 | "win32_window.cpp" 9 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 10 | "Runner.rc" 11 | "runner.exe.manifest" 12 | ) 13 | apply_standard_settings(${BINARY_NAME}) 14 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 15 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 16 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 17 | add_dependencies(${BINARY_NAME} flutter_assemble) 18 | -------------------------------------------------------------------------------- /example/windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #ifdef FLUTTER_BUILD_NUMBER 64 | #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0 67 | #endif 68 | 69 | #ifdef FLUTTER_BUILD_NAME 70 | #define VERSION_AS_STRING #FLUTTER_BUILD_NAME 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "com.one" "\0" 93 | VALUE "FileDescription", "Demonstrates how to use the file_saver plugin." "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "file_saver_example" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2021 com.one. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "file_saver_example.exe" "\0" 98 | VALUE "ProductName", "file_saver_example" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(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 | return true; 30 | } 31 | 32 | void FlutterWindow::OnDestroy() { 33 | if (flutter_controller_) { 34 | flutter_controller_ = nullptr; 35 | } 36 | 37 | Win32Window::OnDestroy(); 38 | } 39 | 40 | LRESULT 41 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 42 | WPARAM const wparam, 43 | LPARAM const lparam) noexcept { 44 | // Give Flutter, including plugins, an opportunity to handle window messages. 45 | if (flutter_controller_) { 46 | std::optional result = 47 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 48 | lparam); 49 | if (result) { 50 | return *result; 51 | } 52 | } 53 | 54 | switch (message) { 55 | case WM_FONTCHANGE: 56 | flutter_controller_->engine()->ReloadSystemFonts(); 57 | break; 58 | } 59 | 60 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 61 | } 62 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "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 | -------------------------------------------------------------------------------- /example/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.CreateAndShow(L"file_saver_example", 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 | -------------------------------------------------------------------------------- /example/windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /example/windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/example/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /example/windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /example/windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr); 51 | if (target_length == 0) { 52 | return std::string(); 53 | } 54 | std::string utf8_string; 55 | utf8_string.resize(target_length); 56 | int converted_length = ::WideCharToMultiByte( 57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 58 | -1, utf8_string.data(), 59 | target_length, nullptr, nullptr); 60 | if (converted_length == 0) { 61 | return std::string(); 62 | } 63 | return utf8_string; 64 | } 65 | -------------------------------------------------------------------------------- /example/windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /example/windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates and shows a win32 window with |title| and position and size using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size to will treat the width height passed in to this function 35 | // as logical pixels and scale to appropriate for the default monitor. Returns 36 | // true if the window was created successfully. 37 | bool CreateAndShow(const std::wstring& title, 38 | const Point& origin, 39 | const Size& size); 40 | 41 | // Release OS resources associated with window. 42 | void Destroy(); 43 | 44 | // Inserts |content| into the window tree. 45 | void SetChildContent(HWND content); 46 | 47 | // Returns the backing Window handle to enable clients to set icon and other 48 | // window properties. Returns nullptr if the window has been destroyed. 49 | HWND GetHandle(); 50 | 51 | // If true, closing this window will quit the application. 52 | void SetQuitOnClose(bool quit_on_close); 53 | 54 | // Return a RECT representing the bounds of the current client area. 55 | RECT GetClientArea(); 56 | 57 | protected: 58 | // Processes and route salient window messages for mouse handling, 59 | // size change and DPI. Delegates handling of these to member overloads that 60 | // inheriting classes can handle. 61 | virtual LRESULT MessageHandler(HWND window, 62 | UINT const message, 63 | WPARAM const wparam, 64 | LPARAM const lparam) noexcept; 65 | 66 | // Called when CreateAndShow is called, allowing subclass window-related 67 | // setup. Subclasses should return false if setup fails. 68 | virtual bool OnCreate(); 69 | 70 | // Called when Destroy is called. 71 | virtual void OnDestroy(); 72 | 73 | private: 74 | friend class WindowClassRegistrar; 75 | 76 | // OS callback called by message pump. Handles the WM_NCCREATE message which 77 | // is passed when the non-client area is being created and enables automatic 78 | // non-client DPI scaling so that the non-client area automatically 79 | // responsponds to changes in DPI. All other messages are handled by 80 | // MessageHandler. 81 | static LRESULT CALLBACK WndProc(HWND const window, 82 | UINT const message, 83 | WPARAM const wparam, 84 | LPARAM const lparam) noexcept; 85 | 86 | // Retrieves a class instance pointer for |window| 87 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 88 | 89 | bool quit_on_close_ = false; 90 | 91 | // window handle for top level window. 92 | HWND window_handle_ = nullptr; 93 | 94 | // window handle for hosted content. 95 | HWND child_content_ = nullptr; 96 | }; 97 | 98 | #endif // RUNNER_WIN32_WINDOW_H_ 99 | -------------------------------------------------------------------------------- /file_saver.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /images/android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/images/android.png -------------------------------------------------------------------------------- /images/iOSXcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/images/iOSXcode.png -------------------------------------------------------------------------------- /images/ios.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/images/ios.png -------------------------------------------------------------------------------- /images/macOSXcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/images/macOSXcode.png -------------------------------------------------------------------------------- /images/macos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/images/macos.png -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | /Flutter/ephemeral/ 38 | /Flutter/flutter_export_environment.sh -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incrediblezayed/file_saver/43d71bba4efccf40c1b246c28b91ba8c57f8b2dc/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/Dialog.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dialog.swift 3 | // file_saver 4 | // 5 | // Created by Hassan Ansari on 22/06/21. 6 | // 7 | 8 | import Foundation 9 | 10 | class Dialog:NSObject, UIDocumentPickerDelegate { 11 | private var result: FlutterResult? 12 | private var tempURL: URL? 13 | private var fileManager = FileManager.default 14 | private var bytes: [UInt8]? 15 | 16 | func openFileManager(byteData: [UInt8],fileName: String, ext: String, result: @escaping FlutterResult){ 17 | self.result = result 18 | self.bytes = byteData 19 | guard let viewController = UIApplication.shared.keyWindow?.rootViewController else { 20 | result(FlutterError(code: "failure", message: "Failed to launch document Picker", details: nil)) 21 | return 22 | } 23 | let temp = NSTemporaryDirectory() 24 | let fileURL = NSURL.fileURL(withPathComponents: [temp, fileName+"."+ext]) 25 | do { 26 | let d = Data(bytes: byteData, count: byteData.count) 27 | try d.write(to: fileURL!) 28 | 29 | } catch { 30 | result(FlutterError(code: "creating_temp_file_failed", 31 | message: error.localizedDescription, 32 | details: nil) 33 | ) 34 | return 35 | } 36 | self.tempURL = fileURL 37 | var docPicker: UIDocumentPickerViewController? 38 | if #available(iOS 14.0, *) { 39 | docPicker = UIDocumentPickerViewController(forExporting: [fileURL!], asCopy: true) 40 | } else { 41 | docPicker = UIDocumentPickerViewController(url: fileURL!, in: .exportToService) 42 | } 43 | docPicker!.delegate = self 44 | viewController.present(docPicker!, animated: true, completion: nil) 45 | } 46 | 47 | private func deleteTemp() { 48 | if tempURL != nil { 49 | do{ if fileManager.fileExists(atPath: tempURL!.path) { 50 | try fileManager.removeItem(at: tempURL!) 51 | } 52 | tempURL = nil 53 | 54 | } 55 | catch { 56 | print(error.localizedDescription) 57 | } 58 | } 59 | } 60 | 61 | func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { 62 | deleteTemp() 63 | print("Cancelled") 64 | result?(nil) 65 | } 66 | 67 | func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) { 68 | deleteTemp() 69 | 70 | print("in didPickDocumentAt " + url.path) 71 | 72 | result?(url.path) 73 | } 74 | 75 | func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { 76 | deleteTemp() 77 | 78 | print("in didPickDocumentAt " + urls[0].path) 79 | 80 | result?(urls[0].path) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /ios/Classes/FileSaverPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface FileSaverPlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /ios/Classes/FileSaverPlugin.m: -------------------------------------------------------------------------------- 1 | #import "FileSaverPlugin.h" 2 | #if __has_include() 3 | #import 4 | #else 5 | // Support project import fallback if the generated compatibility header 6 | // is not copied when this plugin is created as a library. 7 | // https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 8 | #import "file_saver-Swift.h" 9 | #endif 10 | 11 | @implementation FileSaverPlugin 12 | + (void)registerWithRegistrar:(NSObject*)registrar { 13 | [SwiftFileSaverPlugin registerWithRegistrar:registrar]; 14 | } 15 | @end 16 | -------------------------------------------------------------------------------- /ios/Classes/SwiftFileSaverPlugin.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | 4 | public class SwiftFileSaverPlugin: NSObject, FlutterPlugin { 5 | public static func register(with registrar: FlutterPluginRegistrar) { 6 | let channel = FlutterMethodChannel(name: "file_saver", binaryMessenger: registrar.messenger()) 7 | let instance = SwiftFileSaverPlugin() 8 | registrar.addMethodCallDelegate(instance, channel: channel) 9 | } 10 | var dialog = Dialog() 11 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 12 | guard call.method=="saveAs" else { 13 | result(FlutterMethodNotImplemented) 14 | return 15 | } 16 | if call.method=="saveAs" { 17 | guard let arguments = call.arguments as? [String: Any?] else { 18 | result(FlutterError(code: "Invalid Arguments", message: "Invalid Arguments were supplied", details: nil)) 19 | return 20 | } 21 | let params = Params(arguments) 22 | if params.bytes==nil||params.ext==nil||params.fileName==nil { 23 | print("Invalid Arguments") 24 | result(FlutterError(code: "Invalid_Arguments", message: "Some Of the arguments are null", details: nil)) 25 | }else{ 26 | dialog.openFileManager(byteData: params.bytes!, fileName: params.fileName!,ext: params.ext!, result: result) 27 | } 28 | } else { 29 | result("iOS no supported method found") 30 | } 31 | // result("iOS " + UIDevice.current.systemVersion) 32 | } 33 | } 34 | 35 | struct Params { 36 | let fileName: String? 37 | let bytes: [UInt8]? 38 | let ext: String? 39 | init(_ d: [String: Any?]) { 40 | fileName = d["name"] as? String 41 | let uint8List = d["bytes"] as? FlutterStandardTypedData 42 | if(uint8List==nil){ 43 | bytes = nil 44 | }else{ 45 | bytes = [UInt8](uint8List!.data) 46 | } 47 | ext = d["ext"] as? String 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ios/file_saver.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint file_saver.podspec` to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'file_saver' 7 | s.version = '0.0.1' 8 | s.summary = 'A new flutter plugin project.' 9 | s.description = <<-DESC 10 | A new flutter plugin project. 11 | DESC 12 | s.homepage = 'http://example.com' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Your Company' => 'email@example.com' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.dependency 'Flutter' 18 | s.platform = :ios, '8.0' 19 | 20 | # Flutter.framework does not contain a i386 slice. 21 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } 22 | s.swift_version = '5.0' 23 | end 24 | -------------------------------------------------------------------------------- /lib/file_saver.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:dio/dio.dart'; 5 | import 'package:file_saver/src/models/file.model.dart'; 6 | import 'package:file_saver/src/models/link_details.dart'; 7 | import 'package:file_saver/src/saver.dart'; 8 | import 'package:file_saver/src/utils/helpers.dart'; 9 | import 'package:file_saver/src/utils/mime_types.dart'; 10 | import 'package:flutter/foundation.dart'; 11 | 12 | export 'package:file_saver/src/models/link_details.dart'; 13 | export 'package:file_saver/src/utils/mime_types.dart'; 14 | 15 | class FileSaver { 16 | final String _somethingWentWrong = 17 | 'Something went wrong, please report the issue https://www.github.com/incrediblezayed/file_saver/issues'; 18 | late String directory = _somethingWentWrong; 19 | 20 | ///instance of file saver 21 | static FileSaver get instance => FileSaver(); 22 | 23 | late Saver _saver; 24 | 25 | ///[saveFile] main method which saves the file for all platforms. 26 | /// 27 | /// [name]: Name of your file. 28 | /// 29 | /// [bytes]: Encoded File for saving 30 | /// Or 31 | /// [file]: File to be saved. 32 | /// Or 33 | /// [filePath]: Path of file to be saved. 34 | /// Or 35 | /// [link]: Link & header of file to be saved. 36 | /// [LinkDetails] is a model which contains [link] & [headers] 37 | /// LinkDetails( 38 | /// link: 'https://www.google.com', 39 | /// headers: { 40 | /// 'your-header': 'header-value', 41 | /// }, 42 | /// ), 43 | /// 44 | /// Out of these 4 parameters, only one is required. 45 | /// 46 | /// [ext]: Extension of file. 47 | /// 48 | /// mimeType (Mainly required for web): MimeType from enum MimeType.. 49 | /// 50 | /// More Mimetypes will be added in future 51 | Future saveFile({ 52 | required String name, 53 | Uint8List? bytes, 54 | File? file, 55 | String? filePath, 56 | LinkDetails? link, 57 | String ext = '', 58 | MimeType mimeType = MimeType.other, 59 | String? customMimeType, 60 | Dio? dioClient, 61 | Uint8List Function(dynamic data)? transformDioResponse, 62 | }) async { 63 | if (mimeType == MimeType.custom && customMimeType == null) { 64 | throw Exception( 65 | 'customMimeType is required when mimeType is MimeType.custom'); 66 | } 67 | String extension = Helpers.getExtension(extension: ext); 68 | final isFile = file != null || filePath != null; 69 | if (!isFile) { 70 | bytes = bytes ?? 71 | await Helpers.getBytes( 72 | file: file, 73 | filePath: filePath, 74 | link: link, 75 | dioClient: dioClient, 76 | transformDioResponse: transformDioResponse, 77 | ); 78 | } 79 | try { 80 | if (isFile) { 81 | directory = await saveFileOnly( 82 | name: name, 83 | file: file ?? File(filePath!), 84 | ext: extension, 85 | mimeType: mimeType, 86 | ) ?? 87 | _somethingWentWrong; 88 | } else { 89 | _saver = Saver( 90 | fileModel: FileModel( 91 | name: name, 92 | bytes: bytes!, 93 | ext: extension, 94 | mimeType: 95 | mimeType.type.isEmpty ? customMimeType! : mimeType.type)); 96 | directory = await _saver.save() ?? _somethingWentWrong; 97 | } 98 | return directory; 99 | } catch (e) { 100 | rethrow; 101 | } 102 | } 103 | 104 | Future saveFileOnly( 105 | {required String name, 106 | required File file, 107 | String ext = '', 108 | MimeType mimeType = MimeType.other, 109 | String? customMimeType}) async { 110 | try { 111 | final applicationDirectory = await Helpers.getDirectory(); 112 | 113 | return (await file.copy('$applicationDirectory/$name$ext')).path; 114 | } catch (e) { 115 | rethrow; 116 | } 117 | } 118 | 119 | /// [saveAs] This method will open a Save As File dialog where user can select the location for saving file. 120 | /// 121 | /// [name]: Name of your file. 122 | /// 123 | /// [bytes]: Encoded File for saving 124 | /// Or 125 | /// [file]: File to be saved. 126 | /// Or 127 | /// [filePath]: Path of file to be saved. 128 | /// Or 129 | /// [link]: Link of file to be saved. 130 | /// [LinkDetails] is a model which contains [link] & [headers] 131 | /// LinkDetails( 132 | /// link: 'https://www.google.com', 133 | /// headers: { 134 | /// 'your-header': 'header-value', 135 | /// }, 136 | /// ), 137 | /// 138 | /// Out of these 4 parameters, only one is required. 139 | /// 140 | /// [ext]: Extension of file. 141 | /// 142 | /// mimeType (Mainly required for web): MimeType from enum MimeType.. 143 | /// 144 | Future saveAs({ 145 | required String name, 146 | Uint8List? bytes, 147 | File? file, 148 | String? filePath, 149 | LinkDetails? link, 150 | required String ext, 151 | required MimeType mimeType, 152 | String? customMimeType, 153 | Dio? dioClient, 154 | Uint8List Function(dynamic data)? transformDioResponse, 155 | }) async { 156 | if (mimeType == MimeType.custom && customMimeType == null) { 157 | throw Exception( 158 | 'customMimeType is required when mimeType is MimeType.custom'); 159 | } 160 | bytes = bytes ?? 161 | await Helpers.getBytes( 162 | file: file, 163 | filePath: filePath, 164 | link: link, 165 | dioClient: dioClient, 166 | transformDioResponse: transformDioResponse, 167 | ); 168 | 169 | _saver = Saver( 170 | fileModel: FileModel( 171 | name: name, 172 | bytes: bytes, 173 | ext: ext, 174 | mimeType: 175 | mimeType == MimeType.custom ? customMimeType! : mimeType.type)); 176 | String? path = await _saver.saveAs(); 177 | return path; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /lib/file_saver_web.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:js_interop'; 3 | 4 | import 'package:file_saver/src/models/file.model.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:flutter_web_plugins/flutter_web_plugins.dart'; 7 | // In order to *not* need this ignore, consider extracting the "web" version 8 | // of your plugin as a separate package, instead of inlining it in the same 9 | // package as the core of your plugin. 10 | // ignore: avoid_web_libraries_in_flutter 11 | import 'package:web/web.dart'; 12 | 13 | /// A web implementation of the FileSaver plugin. 14 | class FileSaverWeb { 15 | static void registerWith(Registrar registrar) { 16 | final MethodChannel channel = MethodChannel( 17 | 'file_saver', 18 | const StandardMethodCodec(), 19 | registrar, 20 | ); 21 | 22 | final pluginInstance = FileSaverWeb(); 23 | channel.setMethodCallHandler(pluginInstance.handleMethodCall); 24 | } 25 | 26 | Future handleMethodCall(MethodCall call) async { 27 | switch (call.method) { 28 | case 'saveFile': 29 | String args = call.arguments; 30 | 31 | return downloadFile(FileModel.fromJson(args)); 32 | default: 33 | throw PlatformException( 34 | code: 'Unimplemented', 35 | details: 'file_saver for web doesn\'t implement \'${call.method}\'', 36 | ); 37 | } 38 | } 39 | 40 | static Future downloadFile(FileModel fileModel) async { 41 | bool success = false; 42 | 43 | try { 44 | String url = URL.createObjectURL( 45 | Blob( 46 | [fileModel.bytes.toJS].toJS, 47 | BlobPropertyBag(type: fileModel.mimeType), 48 | ), 49 | ); 50 | 51 | Document htmlDocument = document; 52 | HTMLAnchorElement anchor = 53 | htmlDocument.createElement('a') as HTMLAnchorElement; 54 | anchor.href = url; 55 | anchor.style.display = fileModel.name + fileModel.ext; 56 | anchor.download = fileModel.name + fileModel.ext; 57 | document.body!.add(anchor); 58 | anchor.click(); 59 | anchor.remove(); 60 | success = true; 61 | } catch (e) { 62 | rethrow; 63 | } 64 | return success; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/src/models/file.model.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs, sort_constructors_first 2 | import 'dart:convert'; 3 | 4 | import 'package:flutter/foundation.dart'; 5 | 6 | class FileModel { 7 | final String name; 8 | final Uint8List bytes; 9 | final String ext; 10 | final String mimeType; 11 | FileModel({ 12 | required this.name, 13 | required this.bytes, 14 | required this.ext, 15 | required this.mimeType, 16 | }); 17 | 18 | FileModel copyWith({ 19 | String? name, 20 | Uint8List? bytes, 21 | String? ext, 22 | String? mimeType, 23 | }) { 24 | return FileModel( 25 | name: name ?? this.name, 26 | bytes: bytes ?? this.bytes, 27 | ext: ext ?? this.ext, 28 | mimeType: mimeType ?? this.mimeType, 29 | ); 30 | } 31 | 32 | Map toMap() { 33 | return { 34 | 'name': name, 35 | 'bytes': bytes, 36 | 'ext': ext, 37 | 'mimeType': mimeType, 38 | }; 39 | } 40 | 41 | factory FileModel.fromMap(Map map) { 42 | return FileModel( 43 | name: map['name'] as String, 44 | bytes: Uint8List.fromList(List.from(map['bytes'] as List)), 45 | ext: map['ext'] as String, 46 | mimeType: map['mimeType'] as String, 47 | ); 48 | } 49 | 50 | String toJson() => json.encode(toMap()); 51 | 52 | factory FileModel.fromJson(String source) => 53 | FileModel.fromMap(json.decode(source) as Map); 54 | } 55 | -------------------------------------------------------------------------------- /lib/src/models/link_details.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: public_member_api_docs, sort_constructors_first 2 | import 'package:dio/dio.dart'; 3 | import 'package:flutter/foundation.dart'; 4 | 5 | class LinkDetails { 6 | final String link; 7 | final String method; 8 | final Object? body; 9 | final Map? headers; 10 | final Map? queryParameters; 11 | final ResponseType responseType; 12 | LinkDetails({ 13 | required this.link, 14 | this.headers, 15 | this.body, 16 | this.method = 'GET', 17 | this.queryParameters, 18 | this.responseType = ResponseType.bytes, 19 | }); 20 | 21 | @override 22 | bool operator ==(covariant LinkDetails other) { 23 | if (identical(this, other)) return true; 24 | 25 | return other.link == link && 26 | other.method == method && 27 | other.body == body && 28 | mapEquals(other.headers, headers) && 29 | mapEquals(other.queryParameters, queryParameters) && 30 | other.responseType == responseType; 31 | } 32 | 33 | @override 34 | int get hashCode { 35 | return link.hashCode ^ 36 | method.hashCode ^ 37 | body.hashCode ^ 38 | headers.hashCode ^ 39 | queryParameters.hashCode ^ 40 | responseType.hashCode; 41 | } 42 | 43 | @override 44 | String toString() { 45 | return 'LinkDetails(link: $link, method: $method, body: $body, headers: $headers, queryParameters: $queryParameters, responseType: $responseType)'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/src/platform_handler/platform_handler.dart: -------------------------------------------------------------------------------- 1 | import 'package:file_saver/src/models/file.model.dart'; 2 | import 'package:file_saver/src/platform_handler/platform_handler_stub.dart' 3 | // ignore: uri_does_not_exist 4 | if (dart.library.js_interop) 'package:file_saver/src/platform_handler/platform_handler_web.dart' 5 | // ignore: uri_does_not_exist 6 | if (dart.library.io) 'package:file_saver/src/platform_handler/platform_handler_all.dart'; 7 | 8 | abstract class PlatformHandler { 9 | static PlatformHandler get instance { 10 | return getPlatformHandler(); 11 | } 12 | 13 | Future saveFile(FileModel fileModel); 14 | 15 | Future saveAs(FileModel fileModel); 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/platform_handler/platform_handler_all.dart: -------------------------------------------------------------------------------- 1 | import 'dart:developer'; 2 | import 'dart:io'; 3 | import 'dart:typed_data'; 4 | 5 | import 'package:file_saver/src/models/file.model.dart'; 6 | import 'package:file_saver/src/platform_handler/platform_handler.dart'; 7 | import 'package:file_saver/src/utils/helpers.dart'; 8 | import 'package:flutter/services.dart'; 9 | 10 | PlatformHandler getPlatformHandler() { 11 | return PlatformHandlerAll(); 12 | } 13 | 14 | class PlatformHandlerAll extends PlatformHandler { 15 | final MethodChannel _channel = const MethodChannel('file_saver'); 16 | final String _saveAs = 'saveAs'; 17 | final String _saveFile = 'saveFile'; 18 | 19 | final String _somethingWentWrong = 20 | 'Something went wrong, please report the issue https://www.github.com/incrediblezayed/file_saver/issues'; 21 | late String directory = _somethingWentWrong; 22 | 23 | final String _issueLink = 'https://www.github.com/incrediblezayed/file_saver/issues'; 24 | 25 | Future saveFileForAndroid(FileModel fileModel) async { 26 | try { 27 | directory = await _channel.invokeMethod(_saveFile, fileModel.toMap()) ?? ''; 28 | return directory; 29 | } catch (e) { 30 | rethrow; 31 | } 32 | } 33 | 34 | Future saveFileForOtherPlatforms(FileModel fileModel) async { 35 | String path = ''; 36 | path = await Helpers.getDirectory() ?? ''; 37 | if (path == '') { 38 | log('The path was found null or empty, please report the issue at $_issueLink'); 39 | throw Exception('The path was found null or empty'); 40 | } else { 41 | final slash = Helpers.getFilePathSlash(); 42 | String filePath = '$path$slash${fileModel.name}${fileModel.ext}'; 43 | final File file = File(filePath); 44 | await file.writeAsBytes(fileModel.bytes); 45 | bool exist = await file.exists(); 46 | if (exist) { 47 | directory = file.path; 48 | } else { 49 | log('File was not created'); 50 | } 51 | } 52 | return directory; 53 | } 54 | 55 | @override 56 | Future saveFile(FileModel fileModel) async { 57 | if (Platform.isAndroid) { 58 | return await saveFileForAndroid(fileModel); 59 | } else { 60 | return await saveFileForOtherPlatforms(fileModel); 61 | } 62 | } 63 | 64 | ///Open File Manager 65 | @override 66 | Future saveAs(FileModel fileModel) async { 67 | String? path; 68 | if (Platform.isAndroid || Platform.isIOS || Platform.isMacOS) { 69 | path = await _channel.invokeMethod(_saveAs, fileModel.toMap()); 70 | } else if (Platform.isWindows) { 71 | final Int64List? bytes = await _channel.invokeMethod('saveAs', fileModel.toMap()); 72 | path = bytes == null ? null : String.fromCharCodes(bytes); 73 | } else { 74 | throw UnimplementedError('Unimplemented Error'); 75 | } 76 | return path; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/src/platform_handler/platform_handler_stub.dart: -------------------------------------------------------------------------------- 1 | import 'package:file_saver/src/platform_handler/platform_handler.dart'; 2 | 3 | PlatformHandler getPlatformHandler() { 4 | throw UnsupportedError('Cannot create PlatformHandler'); 5 | } 6 | -------------------------------------------------------------------------------- /lib/src/platform_handler/platform_handler_web.dart: -------------------------------------------------------------------------------- 1 | import 'package:file_saver/file_saver_web.dart'; 2 | import 'package:file_saver/src/models/file.model.dart'; 3 | import 'package:file_saver/src/platform_handler/platform_handler.dart'; 4 | 5 | PlatformHandler getPlatformHandler() { 6 | return PlatformHandlerWeb(); 7 | } 8 | 9 | class PlatformHandlerWeb extends PlatformHandler { 10 | @override 11 | Future saveFile(FileModel fileModel) async { 12 | bool result = await FileSaverWeb.downloadFile(fileModel); 13 | if (result) { 14 | return 'Downloads'; 15 | } 16 | return null; 17 | } 18 | 19 | @override 20 | Future saveAs(FileModel fileModel) async { 21 | throw UnimplementedError('saveAs is not implemented on web yet'); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/saver.dart: -------------------------------------------------------------------------------- 1 | import 'package:file_saver/src/models/file.model.dart'; 2 | import 'package:file_saver/src/platform_handler/platform_handler.dart'; 3 | 4 | class Saver { 5 | final FileModel fileModel; 6 | Saver({required this.fileModel}); 7 | final PlatformHandler _platformHandler = PlatformHandler.instance; 8 | Future save() async { 9 | return await _platformHandler.saveFile(fileModel); 10 | } 11 | 12 | Future saveAs() async { 13 | return await _platformHandler.saveAs(fileModel); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/utils/helpers.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:developer'; 3 | import 'dart:io'; 4 | 5 | import 'package:dio/dio.dart'; 6 | import 'package:file_saver/src/models/link_details.dart'; 7 | import 'package:flutter/foundation.dart'; 8 | import 'package:path_provider/path_provider.dart' as path_provider; 9 | import 'package:path_provider_linux/path_provider_linux.dart' as path_provider_linux; 10 | import 'package:path_provider_windows/path_provider_windows.dart' as path_provder_windows; 11 | 12 | ///Helper Class for serveral utility methods 13 | /// 14 | class Helpers { 15 | ///This method provides [Uint8List] from [File] 16 | static Future _getBytesFromFile(File file) async { 17 | return await file.readAsBytes(); 18 | } 19 | 20 | ///This method provides [Uint8List] from file path 21 | static Future _getBytesFromPath(String path) async { 22 | File file = File(path); 23 | return await file.readAsBytes(); 24 | } 25 | 26 | ///This method provides [Uint8List] from link 27 | ///[LinkDetails] is used to provide link and headers 28 | ///[Dio] is used to provide custom dio instance if needed 29 | ///[transformDioResponse] is used to provide custom data transformation 30 | ///Note: Always put the full link within the link field 31 | static Future _getBytesFromLink( 32 | LinkDetails link, { 33 | Dio? dioClient, 34 | Uint8List Function(dynamic data)? transformDioResponse, 35 | }) async { 36 | final dio = dioClient ?? 37 | Dio( 38 | BaseOptions( 39 | headers: link.headers, 40 | method: link.method, 41 | responseType: ResponseType.bytes, 42 | ), 43 | ); 44 | Response response = await dio.request( 45 | link.link, 46 | data: link.body, 47 | queryParameters: link.queryParameters, 48 | ); 49 | if (transformDioResponse != null) { 50 | return transformDioResponse(response.data); 51 | } else { 52 | if (response.data is Uint8List) { 53 | return response.data; 54 | } else if (response.data is List) { 55 | return Uint8List.fromList(response.data); 56 | } else if (response.data is String) { 57 | return utf8.encode(response.data); 58 | } else { 59 | throw Exception( 60 | 'Invalid data type, if you have a different response type, then provide the transformDioResponse function', 61 | ); 62 | } 63 | } 64 | } 65 | 66 | ///This method provides default downloads directory for saving the file for Android, iOS, Linux, Windows, macOS 67 | static Future getDirectory() async { 68 | String? path; 69 | try { 70 | if (Platform.isIOS || Platform.isAndroid) { 71 | path = (await path_provider.getApplicationDocumentsDirectory()).path; 72 | } else if (Platform.isMacOS) { 73 | path = (await path_provider.getDownloadsDirectory())?.path; 74 | } else if (Platform.isWindows) { 75 | path_provder_windows.PathProviderWindows pathWindows = path_provder_windows.PathProviderWindows(); 76 | path = await pathWindows.getDownloadsPath(); 77 | } else if (Platform.isLinux) { 78 | path_provider_linux.PathProviderLinux pathLinux = path_provider_linux.PathProviderLinux(); 79 | path = await pathLinux.getDownloadsPath(); 80 | } 81 | } on Exception catch (e) { 82 | log('Something wemt worng while getting directories'); 83 | log(e.toString()); 84 | rethrow; 85 | } 86 | return path; 87 | } 88 | 89 | static String getFilePathSlash() { 90 | if (Platform.isWindows) { 91 | return '\\'; 92 | } else { 93 | return '/'; 94 | } 95 | } 96 | 97 | ///This method is used to format the extension as per the requirement 98 | static String getExtension({required String extension}) { 99 | if (extension.contains('.')) { 100 | return extension; 101 | } else { 102 | if (extension.isNotEmpty) { 103 | return '.$extension'; 104 | } 105 | return ''; 106 | } 107 | } 108 | 109 | ///This method is used to get [Uint8List] from either [filePath], [link] or [file] 110 | static Future getBytes({ 111 | String? filePath, 112 | LinkDetails? link, 113 | File? file, 114 | Dio? dioClient, 115 | Uint8List Function(dynamic data)? transformDioResponse, 116 | }) async { 117 | assert(filePath != null || link != null || file != null, 'Either filePath or link or file must be provided'); 118 | if (filePath != null) { 119 | return _getBytesFromPath(filePath); 120 | } else { 121 | if (link != null) { 122 | return _getBytesFromLink( 123 | link, 124 | dioClient: dioClient, 125 | transformDioResponse: transformDioResponse, 126 | ); 127 | } else if (file != null) { 128 | return _getBytesFromFile(file); 129 | } else { 130 | throw Exception('Either filePath or link or file must be provided'); 131 | } 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /lib/src/utils/mime_types.dart: -------------------------------------------------------------------------------- 1 | import 'package:collection/collection.dart'; 2 | 3 | ///[MimeType] is an enum for adding filetype for HTML Blob 4 | enum MimeType { 5 | ///[avi] for .avi extension 6 | avi(name: 'AVI', type: 'video/x-msvideo'), 7 | 8 | ///[aac] for .aac extension 9 | aac( 10 | name: 'AAC', 11 | type: 'audio/aac', 12 | ), 13 | 14 | ///[apng] for .apng extension 15 | apng(name: 'APNG', type: 'image/apng'), 16 | 17 | ///[asice] for .asice 18 | asice(name: 'ASICE', type: 'application/vnd.etsi.asic-e+zip'), 19 | 20 | ///[asics] for .asice 21 | asics( 22 | name: 'ASICS', 23 | type: 'application/vnd.etsi.asic-s+zip', 24 | ), 25 | 26 | ///[bDoc] for .asice 27 | bDoc( 28 | name: 'BDoc', 29 | type: 'application/vnd.etsi.asic-e+zip', 30 | ), 31 | 32 | ///[bmp] for .bmp extension 33 | bmp(name: 'Bitmap', type: 'image/bmp'), 34 | 35 | ///[csv] for .csv extension 36 | csv( 37 | name: 'CSV', 38 | type: 'text/csv', 39 | ), 40 | 41 | ///[epub] for .epub extention 42 | epub(name: 'Epub', type: 'application/epub+zip'), 43 | 44 | ///[gif] for .gif extension 45 | gif(name: 'GIF', type: 'image/gif'), 46 | 47 | ///[json] for .json extension 48 | json(name: 'JSON', type: 'application/json'), 49 | 50 | ///[jpeg] for .jpeg extension 51 | jpeg(name: 'JPEG', type: 'image/jpeg'), 52 | 53 | ///[microsoftExcel] for .xlsx extension 54 | microsoftExcel( 55 | name: 'Microsoft Excel', 56 | type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 57 | ), 58 | 59 | ///[microsoftPresentation] for .pptx extension 60 | microsoftPresentation( 61 | name: 'Microsoft Presentation', 62 | type: 63 | 'application/vnd.openxmlformats-officedocument.presentationml.presentation'), 64 | 65 | ///[microsoftWord] for .docx extension 66 | microsoftWord( 67 | name: 'Microsoft Word', 68 | type: 69 | 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'), 70 | 71 | ///[mpeg] for .mpeg extension 72 | mpeg(name: 'MPEG', type: 'video/mpeg'), 73 | 74 | ///[mp3] for .mp3 extension 75 | mp3(name: 'MP3', type: 'audio/mpeg'), 76 | 77 | /// [mp4Video] for .mp4 extension for video files 78 | mp4Video(name: 'MP4 Video', type: 'video/mp4'), 79 | 80 | /// [mp4Audio] for .mp4 extension for audio files 81 | mp4Audio(name: 'MP4 Audio', type: 'audio/mp4'), 82 | 83 | /// [mp4Object] for .mp4 extension for other media files with mp4 extension 84 | mp4Object(name: 'MP4 Object', type: 'application/mp4'), 85 | 86 | ///[other] for other extension 87 | other(name: 'Other', type: 'application/octet-stream'), 88 | 89 | ///[otf] for .otf extension 90 | otf(name: 'OTF', type: 'font/otf'), 91 | 92 | ///[openDocSheets] for .ods extension 93 | openDocSheets( 94 | name: 'Open Document Sheets', 95 | type: 'application/vnd.oasis.opendocument.spreadsheet', 96 | ), 97 | 98 | ///[openDocPresentation] for .odp extension 99 | openDocPresentation( 100 | name: 'Open Document Presentation', 101 | type: 'application/vnd.oasis.opendocument.presentation', 102 | ), 103 | 104 | ///[openDocText] for .odt extension 105 | openDocText( 106 | name: 'Open Document Text', 107 | type: 'application/vnd.oasis.opendocument.text'), 108 | 109 | ///[pdf] for .pdf extension 110 | pdf( 111 | name: 'PDF', 112 | type: 'application/pdf', 113 | ), 114 | 115 | ///[png] for .png extension 116 | png(name: 'PNG', type: 'image/png'), 117 | 118 | ///[rar] for .rar extension 119 | rar( 120 | name: 'RAR', 121 | type: 'application/x-rar-compressed', 122 | ), 123 | 124 | ///[text] for .txt extension 125 | text(name: 'Text', type: 'text/plain'), 126 | 127 | ///[ttf] for .ttf extension 128 | ttf(name: 'TTF', type: 'font/ttf'), 129 | 130 | ///[zip] for .zip extension 131 | zip( 132 | name: 'ZIP', 133 | type: 'application/zip', 134 | ), 135 | 136 | ///Custom mimeType which is not yet added in the enum 137 | custom(name: 'Custom', type: ''); 138 | 139 | final String name; 140 | final String type; 141 | const MimeType({required this.name, required this.type}); 142 | 143 | static MimeType? get(String? name) { 144 | return MimeType.values.firstWhereOrNull((e) => e.name == name); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | set(PROJECT_NAME "file_saver") 3 | project(${PROJECT_NAME} LANGUAGES CXX) 4 | 5 | # This value is used when generating builds using this plugin, so it must 6 | # not be changed 7 | set(PLUGIN_NAME "file_saver_plugin") 8 | 9 | add_library(${PLUGIN_NAME} SHARED 10 | "file_saver_plugin.cc" 11 | ) 12 | apply_standard_settings(${PLUGIN_NAME}) 13 | set_target_properties(${PLUGIN_NAME} PROPERTIES 14 | CXX_VISIBILITY_PRESET hidden) 15 | target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) 16 | target_include_directories(${PLUGIN_NAME} INTERFACE 17 | "${CMAKE_CURRENT_SOURCE_DIR}/include") 18 | target_link_libraries(${PLUGIN_NAME} PRIVATE flutter) 19 | target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK) 20 | 21 | # List of absolute paths to libraries that should be bundled with the plugin 22 | set(file_saver_bundled_libraries 23 | "" 24 | PARENT_SCOPE 25 | ) 26 | -------------------------------------------------------------------------------- /linux/file_saver_plugin.cc: -------------------------------------------------------------------------------- 1 | #include "include/file_saver/file_saver_plugin.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #define FILE_SAVER_PLUGIN(obj) \ 10 | (G_TYPE_CHECK_INSTANCE_CAST((obj), file_saver_plugin_get_type(), \ 11 | FileSaverPlugin)) 12 | 13 | struct _FileSaverPlugin { 14 | GObject parent_instance; 15 | }; 16 | 17 | G_DEFINE_TYPE(FileSaverPlugin, file_saver_plugin, g_object_get_type()) 18 | 19 | // Called when a method call is received from Flutter. 20 | static void file_saver_plugin_handle_method_call( 21 | FileSaverPlugin* self, 22 | FlMethodCall* method_call) { 23 | g_autoptr(FlMethodResponse) response = nullptr; 24 | 25 | const gchar* method = fl_method_call_get_name(method_call); 26 | 27 | if (strcmp(method, "getPlatformVersion") == 0) { 28 | struct utsname uname_data = {}; 29 | uname(&uname_data); 30 | g_autofree gchar *version = g_strdup_printf("Linux %s", uname_data.version); 31 | g_autoptr(FlValue) result = fl_value_new_string(version); 32 | response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); 33 | } else { 34 | response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); 35 | } 36 | 37 | fl_method_call_respond(method_call, response, nullptr); 38 | } 39 | 40 | static void file_saver_plugin_dispose(GObject* object) { 41 | G_OBJECT_CLASS(file_saver_plugin_parent_class)->dispose(object); 42 | } 43 | 44 | static void file_saver_plugin_class_init(FileSaverPluginClass* klass) { 45 | G_OBJECT_CLASS(klass)->dispose = file_saver_plugin_dispose; 46 | } 47 | 48 | static void file_saver_plugin_init(FileSaverPlugin* self) {} 49 | 50 | static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call, 51 | gpointer user_data) { 52 | FileSaverPlugin* plugin = FILE_SAVER_PLUGIN(user_data); 53 | file_saver_plugin_handle_method_call(plugin, method_call); 54 | } 55 | 56 | void file_saver_plugin_register_with_registrar(FlPluginRegistrar* registrar) { 57 | FileSaverPlugin* plugin = FILE_SAVER_PLUGIN( 58 | g_object_new(file_saver_plugin_get_type(), nullptr)); 59 | 60 | g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); 61 | g_autoptr(FlMethodChannel) channel = 62 | fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar), 63 | "file_saver", 64 | FL_METHOD_CODEC(codec)); 65 | fl_method_channel_set_method_call_handler(channel, method_call_cb, 66 | g_object_ref(plugin), 67 | g_object_unref); 68 | 69 | g_object_unref(plugin); 70 | } 71 | -------------------------------------------------------------------------------- /linux/include/file_saver/file_saver_plugin.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_PLUGIN_FILE_SAVER_PLUGIN_H_ 2 | #define FLUTTER_PLUGIN_FILE_SAVER_PLUGIN_H_ 3 | 4 | #include 5 | 6 | G_BEGIN_DECLS 7 | 8 | #ifdef FLUTTER_PLUGIN_IMPL 9 | #define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default"))) 10 | #else 11 | #define FLUTTER_PLUGIN_EXPORT 12 | #endif 13 | 14 | typedef struct _FileSaverPlugin FileSaverPlugin; 15 | typedef struct { 16 | GObjectClass parent_class; 17 | } FileSaverPluginClass; 18 | 19 | FLUTTER_PLUGIN_EXPORT GType file_saver_plugin_get_type(); 20 | 21 | FLUTTER_PLUGIN_EXPORT void file_saver_plugin_register_with_registrar( 22 | FlPluginRegistrar* registrar); 23 | 24 | G_END_DECLS 25 | 26 | #endif // FLUTTER_PLUGIN_FILE_SAVER_PLUGIN_H_ 27 | -------------------------------------------------------------------------------- /macos/Classes/Dialog.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dialog.swift 3 | // file_saver 4 | // 5 | // Created by Hassan Ansari on 23/06/21. 6 | // 7 | 8 | import Foundation 9 | import FlutterMacOS 10 | 11 | class Dialog: NSObject { 12 | 13 | func openSaveAsDialog(params: Params, result: @escaping FlutterResult){ 14 | let panel = NSSavePanel() 15 | panel.directoryURL = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first 16 | panel.nameFieldStringValue = params.fileName!+"."+params.ext! 17 | panel.canCreateDirectories = true 18 | panel.allowsOtherFileTypes = true 19 | panel.title = Bundle.main.infoDictionary?[kCFBundleNameKey as String] as? String 20 | panel.level = .mainMenu 21 | panel.begin{ (response) in if response.rawValue == NSApplication.ModalResponse.OK.rawValue{ 22 | guard let url = panel.url else {return } 23 | self.saveFile(byteData: params.bytes!, url: url, fileName: params.fileName ?? "File", ext: params.ext!, result: result) 24 | } 25 | else{ 26 | return 27 | } 28 | } 29 | } 30 | private func saveFile(byteData: [UInt8],url: URL,fileName: String, ext: String, result: @escaping FlutterResult){ 31 | do { 32 | let data = Data(byteData) 33 | try data.write(to: url) 34 | result(url.absoluteString) 35 | } catch { 36 | result("Failed to save file") 37 | } 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /macos/Classes/FileSaverPlugin.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | public class FileSaverPlugin: NSObject, FlutterPlugin { 5 | public static func register(with registrar: FlutterPluginRegistrar) { 6 | let channel = FlutterMethodChannel(name: "file_saver", binaryMessenger: registrar.messenger) 7 | let instance = FileSaverPlugin() 8 | registrar.addMethodCallDelegate(instance, channel: channel) 9 | } 10 | 11 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 12 | switch call.method { 13 | case "saveAs": 14 | guard let arguments = call.arguments as? [String: Any?] else { 15 | result(FlutterError(code: "Invalid Arguments", message: "Invalid Arguments were supplied", details: nil)) 16 | return 17 | } 18 | let params = Params(arguments) 19 | DispatchQueue.main.async { 20 | let dialog = Dialog() 21 | dialog.openSaveAsDialog(params: params, result: result) 22 | } 23 | default: 24 | result(FlutterMethodNotImplemented) 25 | } 26 | } 27 | 28 | } 29 | 30 | struct Params { 31 | let fileName: String? 32 | let bytes: [UInt8]? 33 | let ext: String? 34 | init(_ d: [String: Any?]) { 35 | fileName = d["name"] as? String 36 | let uint8List = d["bytes"] as? FlutterStandardTypedData 37 | if(uint8List==nil){ 38 | bytes = nil 39 | }else{ 40 | bytes = [UInt8](uint8List!.data) 41 | } 42 | ext = d["ext"] as? String 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /macos/file_saver.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint file_saver.podspec` to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'file_saver' 7 | s.version = '0.0.1' 8 | s.summary = 'A new flutter plugin project.' 9 | s.description = <<-DESC 10 | A new flutter plugin project. 11 | DESC 12 | s.homepage = 'http://example.com' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Your Company' => 'email@example.com' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.dependency 'FlutterMacOS' 18 | 19 | s.platform = :osx, '10.11' 20 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } 21 | s.swift_version = '5.0' 22 | end 23 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: file_saver 2 | description: >- 3 | This package will help you save file with a single method on any platform 4 | including macOS, iOS, Android, Windows, Web, Linux. 5 | version: 0.2.14 6 | repository: https://github.com/incrediblezayed/file_saver 7 | homepage: https://hassanansari.dev 8 | 9 | environment: 10 | sdk: ">=2.17.0 <4.0.0" 11 | flutter: ">=3.0.0" 12 | 13 | dependencies: 14 | collection: ^1.18.0 15 | dio: ^5.6.0 16 | flutter: 17 | sdk: flutter 18 | flutter_web_plugins: 19 | sdk: flutter 20 | path_provider: ^2.1.4 21 | path_provider_linux: ^2.2.1 22 | path_provider_windows: ^2.3.0 23 | web: ^1.0.0 24 | 25 | dev_dependencies: 26 | flutter_lints: ^4.0.0 27 | flutter_test: 28 | sdk: flutter 29 | 30 | flutter: 31 | plugin: 32 | platforms: 33 | android: 34 | package: com.incrediblezayed.file_saver 35 | pluginClass: FileSaverPlugin 36 | ios: 37 | pluginClass: FileSaverPlugin 38 | linux: 39 | pluginClass: FileSaverPlugin 40 | macos: 41 | pluginClass: FileSaverPlugin 42 | windows: 43 | pluginClass: FileSaverPlugin 44 | web: 45 | pluginClass: FileSaverWeb 46 | fileName: file_saver_web.dart 47 | -------------------------------------------------------------------------------- /test/file_saver_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | void main() { 4 | TestWidgetsFlutterBinding.ensureInitialized(); 5 | 6 | setUp(() { 7 | TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger 8 | .setMockMessageHandler('file_saver', (message) => null); 9 | }); 10 | 11 | tearDown(() { 12 | TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger 13 | .setMockMessageHandler('file_saver', (message) => null); 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ 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 | cmake_minimum_required(VERSION 3.15) 2 | set(PROJECT_NAME "file_saver") 3 | project(${PROJECT_NAME} LANGUAGES CXX) 4 | 5 | # This value is used when generating builds using this plugin, so it must 6 | # not be changed 7 | set(PLUGIN_NAME "file_saver_plugin") 8 | 9 | add_library(${PLUGIN_NAME} SHARED 10 | "file_saver_plugin.cpp" 11 | ) 12 | apply_standard_settings(${PLUGIN_NAME}) 13 | set_target_properties(${PLUGIN_NAME} PROPERTIES 14 | CXX_VISIBILITY_PRESET hidden) 15 | target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) 16 | target_include_directories(${PLUGIN_NAME} INTERFACE 17 | "${CMAKE_CURRENT_SOURCE_DIR}/include") 18 | target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) 19 | 20 | # List of absolute paths to libraries that should be bundled with the plugin 21 | set(file_saver_bundled_libraries 22 | "" 23 | PARENT_SCOPE 24 | ) 25 | -------------------------------------------------------------------------------- /windows/Dialog.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define OPEN_FILE_BUTTON 1 5 | #define SAVE_FILE_BUTTON 2 6 | 7 | LRESULT CALLBACK WindowProcedure(HWND, UINT, WPARAM, LPARAM); 8 | 9 | void AddControls(HWND); 10 | 11 | HWND hMainWindow, hEdit; 12 | 13 | int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR args, int nCmdShow) { 14 | WNDCLASS wc = {0}; 15 | 16 | wc.hbrBackground = (HBRUSH) COLOR_WINDOW; 17 | wc.hCursor = LoadCursor(nullptr, IDC_ARROW); 18 | wc.hInstance = hInst; 19 | wc.lpszClassName = reinterpret_cast(L"myWindowClass"); 20 | wc.lpfnWndProc = WindowProcedure; 21 | 22 | if (!RegisterClassW(reinterpret_cast(&wc))) 23 | return -1; 24 | 25 | hMainWindow = CreateWindowW(L"myWindowClass", L"My Window", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 500, 500, 26 | nullptr, nullptr, nullptr, nullptr); 27 | 28 | MSG msg = {nullptr}; 29 | 30 | while (GetMessage(&msg, nullptr, NULL, NULL)) { 31 | TranslateMessage(&msg); 32 | DispatchMessage(&msg); 33 | } 34 | return 0; 35 | } 36 | 37 | void display_file(char *path) { 38 | FILE *file; 39 | file = fopen(path, "rb"); 40 | fseek(file, 0, SEEK_END); 41 | int _size = ftell(file); 42 | rewind(file); 43 | char *data = new char[_size + 1]; 44 | fread(data, _size, 1, file); 45 | // '\0' is terminating character 46 | data[_size] = '\0'; 47 | 48 | SetWindowText(hEdit, data); 49 | 50 | fclose(file); 51 | } 52 | 53 | void open_file(HWND hwnd) { 54 | OPENFILENAME ofn; 55 | 56 | char file_name[100]; 57 | 58 | ZeroMemory(&ofn, sizeof(OPENFILENAME)); 59 | 60 | ofn.lStructSize = sizeof(OPENFILENAME); 61 | ofn.hwndOwner = hwnd; 62 | ofn.lpstrFile = file_name; 63 | ofn.lpstrFile[0] = '\0'; 64 | ofn.nMaxFile = 100; 65 | ofn.lpstrFilter = "All file\0*.*\0Source Files\0*.CPP\0Text Files\0*.TXT\0"; 66 | ofn.nFilterIndex = 1; 67 | 68 | GetOpenFileName(&ofn); 69 | 70 | display_file(ofn.lpstrFile); 71 | 72 | //Path of file show on Dialog Box 73 | //MessageBox(hwnd, NULL, ofn.lpstrFile, MB_OK); 74 | } 75 | 76 | void write_file(char *path) { 77 | FILE *file; 78 | file = fopen(path, "w"); 79 | 80 | int _size = GetWindowTextLength(hEdit); 81 | char *data = new char[_size + 1]; 82 | GetWindowText(hEdit, data, _size + 1); 83 | 84 | fwrite(data, _size + 1, 1, file); 85 | 86 | fclose(file); 87 | 88 | } 89 | 90 | void save_file(HWND hwnd) { 91 | OPENFILENAME ofn; 92 | 93 | char file_name[100]; 94 | 95 | ZeroMemory(&ofn, sizeof(OPENFILENAME)); 96 | 97 | ofn.lStructSize = sizeof(OPENFILENAME); 98 | ofn.hwndOwner = hwnd; 99 | ofn.lpstrFile = file_name; 100 | ofn.lpstrFile[0] = '\0'; 101 | ofn.nMaxFile = 100; 102 | ofn.lpstrFilter = "All file\0*.*\0Source Files\0*.CPP\0Text Files\0*.TXT\0"; 103 | ofn.nFilterIndex = 1; 104 | 105 | GetSaveFileName(&ofn); 106 | 107 | write_file(ofn.lpstrFile); 108 | } 109 | 110 | 111 | LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { 112 | switch (msg) { 113 | case WM_COMMAND: { 114 | switch (wp) { 115 | case OPEN_FILE_BUTTON: 116 | open_file(hwnd); 117 | break; 118 | case SAVE_FILE_BUTTON: 119 | save_file(hwnd); 120 | break; 121 | default: 122 | return 0; 123 | } 124 | } 125 | break; 126 | case WM_CREATE: 127 | AddControls(hwnd); 128 | break; 129 | case WM_DESTROY: 130 | PostQuitMessage(0); 131 | break; 132 | default: 133 | return DefWindowProcW(hwnd, msg, wp, lp); 134 | 135 | } 136 | return 0; 137 | } 138 | 139 | void AddControls(HWND hwnd) { 140 | CreateWindowW(L"Button", L"Open File", WS_VISIBLE | WS_CHILD, 10, 10, 150, 36, hwnd, (HMENU) OPEN_FILE_BUTTON, 141 | nullptr, nullptr); 142 | CreateWindowW(L"Button", L"Save File", WS_VISIBLE | WS_CHILD, 170, 10, 150, 36, hwnd, (HMENU) SAVE_FILE_BUTTON, 143 | nullptr, nullptr); 144 | 145 | hEdit = CreateWindowW(L"Edit", L"", WS_VISIBLE | WS_CHILD | ES_MULTILINE | WS_BORDER | WS_VSCROLL | WS_HSCROLL, 10, 146 | 50, 400, 300, 147 | hwnd, nullptr, nullptr, nullptr); 148 | 149 | } -------------------------------------------------------------------------------- /windows/file_saver_plugin.cpp: -------------------------------------------------------------------------------- 1 | #include "include/file_saver/file_saver_plugin.h" 2 | 3 | // This must be included before many other Windows headers. 4 | #include 5 | 6 | // For getPlatformVersion; remove unless needed for your plugin implementation. 7 | #include 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | namespace { 21 | 22 | class FileSaverPlugin : public flutter::Plugin { 23 | public: 24 | static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar); 25 | 26 | FileSaverPlugin(); 27 | 28 | virtual ~FileSaverPlugin(); 29 | 30 | private: 31 | // Called when a method is called on this plugin's channel from Dart. 32 | void HandleMethodCall( 33 | const flutter::MethodCall &method_call, 34 | std::unique_ptr> result); 35 | }; 36 | 37 | // static 38 | void FileSaverPlugin::RegisterWithRegistrar( 39 | flutter::PluginRegistrarWindows *registrar) { 40 | auto channel = 41 | std::make_unique>( 42 | registrar->messenger(), "file_saver", 43 | &flutter::StandardMethodCodec::GetInstance()); 44 | 45 | auto plugin = std::make_unique(); 46 | 47 | channel->SetMethodCallHandler( 48 | [plugin_pointer = plugin.get()](const auto &call, auto result) { 49 | plugin_pointer->HandleMethodCall(call, std::move(result)); 50 | }); 51 | 52 | registrar->AddPlugin(std::move(plugin)); 53 | } 54 | 55 | FileSaverPlugin::FileSaverPlugin() {} 56 | 57 | FileSaverPlugin::~FileSaverPlugin() {} 58 | 59 | std::vector WideStringToVector(const wchar_t* wideStr) { 60 | std::vector result; 61 | 62 | int length = 0; 63 | while (wideStr[length] != L'\0') { 64 | length++; 65 | } 66 | 67 | for (int i = 0; i < length; i++) { 68 | result.push_back(static_cast(wideStr[i])); 69 | } 70 | 71 | return result; 72 | } 73 | 74 | std::wstring FileExtensionToFileFilter(std::string fileExtension) { 75 | std::string fileExtensionName = fileExtension.substr(1); 76 | for (auto& c : fileExtensionName) c = (char) std::toupper(c); 77 | 78 | std::wstring wideFileExtension = std::wstring(fileExtension.begin(), fileExtension.end()); 79 | std::wstring wideFileExtensionName = std::wstring(fileExtensionName.begin(), fileExtensionName.end()); 80 | return wideFileExtensionName + L" File\0*." + wideFileExtensionName + L"\0\0"; 81 | } 82 | 83 | void FileSaverPlugin::HandleMethodCall( 84 | const flutter::MethodCall &method_call, 85 | std::unique_ptr> result) { 86 | if (method_call.method_name().compare("saveAs") == 0) { 87 | const auto& arguments = *method_call.arguments(); 88 | const auto& mapArgs = std::get(arguments); 89 | 90 | const flutter::EncodableValue& inputFileNameValue = mapArgs.at(flutter::EncodableValue("name")); 91 | const std::string inputFileName = std::get(inputFileNameValue); 92 | 93 | const flutter::EncodableValue& inputExtensionValue = mapArgs.at(flutter::EncodableValue("ext")); 94 | const std::string inputExtension = std::get(inputExtensionValue); 95 | 96 | const std::string defaultFileName = inputFileName + inputExtension; 97 | static wchar_t szFile[MAX_PATH] = L""; 98 | wcscpy_s(szFile, std::wstring(defaultFileName.begin(), defaultFileName.end()).c_str()); 99 | 100 | const std::wstring defaultFileFilter = FileExtensionToFileFilter(inputExtension); 101 | static wchar_t lpstrFilter[MAX_PATH] = L""; 102 | wcscpy_s(lpstrFilter, defaultFileFilter.c_str()); 103 | 104 | OPENFILENAME ofn; 105 | ZeroMemory(&ofn, sizeof(ofn)); 106 | ofn.lStructSize = sizeof(ofn); 107 | ofn.hwndOwner = NULL; 108 | // ofn.lpstrFilter = L"All Files\0*.*\0"; 109 | ofn.lpstrFilter = lpstrFilter; 110 | ofn.lpstrFile = szFile; 111 | ofn.nMaxFile = MAX_PATH; 112 | ofn.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY; 113 | 114 | if (GetSaveFileName(&ofn)) { 115 | std::vector value = WideStringToVector(szFile); 116 | result->Success(flutter::EncodableValue(value)); 117 | } else { 118 | result->Success(flutter::EncodableValue()); 119 | } 120 | 121 | } else { 122 | result->NotImplemented(); 123 | } 124 | } 125 | 126 | } // namespace 127 | 128 | void FileSaverPluginRegisterWithRegistrar( 129 | FlutterDesktopPluginRegistrarRef registrar) { 130 | FileSaverPlugin::RegisterWithRegistrar( 131 | flutter::PluginRegistrarManager::GetInstance() 132 | ->GetRegistrar(registrar)); 133 | } 134 | -------------------------------------------------------------------------------- /windows/file_saver_plugin.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_PLUGIN_FILE_SAVER_PLUGIN_H_ 2 | #define FLUTTER_PLUGIN_FILE_SAVER_PLUGIN_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace file_saver { 10 | 11 | class FileSaverPlugin : public flutter::Plugin { 12 | public: 13 | static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar); 14 | 15 | FileSaverPlugin(); 16 | 17 | virtual ~FileSaverPlugin(); 18 | 19 | // Disallow copy and assign. 20 | FileSaverPlugin(const FileSaverPlugin&) = delete; 21 | FileSaverPlugin& operator=(const FileSaverPlugin&) = delete; 22 | 23 | private: 24 | // Called when a method is called on this plugin's channel from Dart. 25 | void HandleMethodCall( 26 | const flutter::MethodCall &method_call, 27 | std::unique_ptr> result); 28 | }; 29 | 30 | } // namespace file_saver 31 | 32 | #endif // FLUTTER_PLUGIN_FILE_SAVER_PLUGIN_H_ 33 | -------------------------------------------------------------------------------- /windows/file_saver_plugin_c_api.cpp: -------------------------------------------------------------------------------- 1 | #include "include/file_saver/file_saver_plugin_c_api.h" 2 | 3 | #include 4 | 5 | #include "file_saver_plugin.h" 6 | 7 | void FileSaverPluginCApiRegisterWithRegistrar( 8 | FlutterDesktopPluginRegistrarRef registrar) { 9 | file_saver::FileSaverPlugin::RegisterWithRegistrar( 10 | flutter::PluginRegistrarManager::GetInstance() 11 | ->GetRegistrar(registrar)); 12 | } 13 | -------------------------------------------------------------------------------- /windows/include/file_saver/file_saver_plugin.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_PLUGIN_FILE_SAVER_PLUGIN_H_ 2 | #define FLUTTER_PLUGIN_FILE_SAVER_PLUGIN_H_ 3 | 4 | #include 5 | 6 | #ifdef FLUTTER_PLUGIN_IMPL 7 | #define FLUTTER_PLUGIN_EXPORT __declspec(dllexport) 8 | #else 9 | #define FLUTTER_PLUGIN_EXPORT __declspec(dllimport) 10 | #endif 11 | 12 | #if defined(__cplusplus) 13 | extern "C" { 14 | #endif 15 | 16 | FLUTTER_PLUGIN_EXPORT void FileSaverPluginRegisterWithRegistrar( 17 | FlutterDesktopPluginRegistrarRef registrar); 18 | 19 | #if defined(__cplusplus) 20 | } // extern "C" 21 | #endif 22 | 23 | #endif // FLUTTER_PLUGIN_FILE_SAVER_PLUGIN_H_ 24 | -------------------------------------------------------------------------------- /windows/include/file_saver/file_saver_plugin_c_api.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_PLUGIN_FILE_SAVER_PLUGIN_C_API_H_ 2 | #define FLUTTER_PLUGIN_FILE_SAVER_PLUGIN_C_API_H_ 3 | 4 | #include 5 | 6 | #ifdef FLUTTER_PLUGIN_IMPL 7 | #define FLUTTER_PLUGIN_EXPORT __declspec(dllexport) 8 | #else 9 | #define FLUTTER_PLUGIN_EXPORT __declspec(dllimport) 10 | #endif 11 | 12 | #if defined(__cplusplus) 13 | extern "C" { 14 | #endif 15 | 16 | FLUTTER_PLUGIN_EXPORT void FileSaverPluginCApiRegisterWithRegistrar( 17 | FlutterDesktopPluginRegistrarRef registrar); 18 | 19 | #if defined(__cplusplus) 20 | } // extern "C" 21 | #endif 22 | 23 | #endif // FLUTTER_PLUGIN_FILE_SAVER_PLUGIN_C_API_H_ 24 | --------------------------------------------------------------------------------