├── .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 | [](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 | 
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 | 
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 | 
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 | 
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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | xmlns:android
18 |
19 | ^$
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | xmlns:.*
29 |
30 | ^$
31 |
32 |
33 | BY_NAME
34 |
35 |
36 |
37 |
38 |
39 |
40 | .*:id
41 |
42 | http://schemas.android.com/apk/res/android
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | .*:name
52 |
53 | http://schemas.android.com/apk/res/android
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | name
63 |
64 | ^$
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | style
74 |
75 | ^$
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | .*
85 |
86 | ^$
87 |
88 |
89 | BY_NAME
90 |
91 |
92 |
93 |
94 |
95 |
96 | .*
97 |
98 | http://schemas.android.com/apk/res/android
99 |
100 |
101 | ANDROID_ATTRIBUTE_ORDER
102 |
103 |
104 |
105 |
106 |
107 |
108 | .*
109 |
110 | .*
111 |
112 |
113 | BY_NAME
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/android/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
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 |
--------------------------------------------------------------------------------
/android/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
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