diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f010a280e18..c5fa2a0e95d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -159,6 +159,17 @@ updates: - dependency-name: "*" update-types: ["version-update:semver-minor", "version-update:semver-patch"] + - package-ecosystem: "gradle" + directory: "/packages/file_selector/file_selector/example/android/app" + commit-message: + prefix: "[file_selector]" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-minor", "version-update:semver-patch"] + - package-ecosystem: "gradle" directory: "/packages/file_selector/file_selector_android/android" commit-message: diff --git a/CODEOWNERS b/CODEOWNERS index dfc8a29c749..21ef69fc030 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -53,6 +53,7 @@ packages/**/*_web/** @ditman packages/camera/camera_android/** @camsim99 packages/camera/camera_android_camerax/** @camsim99 packages/espresso/** @reidbaker +packages/file_selector/file_selector_android/** @gmackall packages/flutter_plugin_android_lifecycle/** @reidbaker packages/google_maps_flutter/google_maps_flutter_android/** @reidbaker packages/google_sign_in/google_sign_in_android/** @camsim99 diff --git a/packages/file_selector/file_selector/CHANGELOG.md b/packages/file_selector/file_selector/CHANGELOG.md index 5215787e070..089f2d70b7e 100644 --- a/packages/file_selector/file_selector/CHANGELOG.md +++ b/packages/file_selector/file_selector/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.9.5 + +* Adds an endorsed Android implementation. + ## 0.9.4 * Adds `getSaveLocation` and deprecates `getSavePath`. diff --git a/packages/file_selector/file_selector/README.md b/packages/file_selector/file_selector/README.md index dac9746891b..12efb64de74 100644 --- a/packages/file_selector/file_selector/README.md +++ b/packages/file_selector/file_selector/README.md @@ -6,9 +6,9 @@ A Flutter plugin that manages files and interactions with file dialogs. -| | iOS | Linux | macOS | Web | Windows | -|-------------|---------|-------|--------|-----|-------------| -| **Support** | iOS 11+ | Any | 10.14+ | Any | Windows 10+ | +| | Android | iOS | Linux | macOS | Web | Windows | +|-------------|---------|---------|-------|--------|-----|-------------| +| **Support** | SDK 19+ | iOS 11+ | Any | 10.14+ | Any | Windows 10+ | ## Usage @@ -100,23 +100,25 @@ Different platforms support different type group filter options. To avoid filters that cover all platforms you are targeting, or that you conditionally pass different `XTypeGroup`s based on `Platform`. -| | iOS | Linux | macOS | Web | Windows | -|--------------------------|-----|-------|--------|-----|-------------| -| `extensions` | | ✔️ | ✔️ | ✔️ | ✔️ | -| `mimeTypes` | | ✔️ | ✔️† | ✔️ | | -| `uniformTypeIdentifiers` | ✔️ | | ✔️ | | | -| `webWildCards` | | | | ✔️ | | +| | Andoid | iOS | Linux | macOS | Web | Windows | +|--------------------------|--------|-----|-------|--------|-----|-------------| +| `extensions` | ✔️ | | ✔️ | ✔️ | ✔️ | ✔️ | +| `mimeTypes` | ✔️ | | ✔️ | ✔️† | ✔️ | | +| `uniformTypeIdentifiers` | | ✔️ | | ✔️ | | | +| `webWildCards` | | | | | ✔️ | | † `mimeTypes` are not supported on version of macOS earlier than 11 (Big Sur). ### Features supported by platform -| Feature | Description | iOS | Linux | macOS | Windows | Web | -| ---------------------- |----------------------------------- |--------- | ---------- | -------- | ------------ | ----------- | -| Choose a single file | Pick a file/image | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | -| Choose multiple files | Pick multiple files/images | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | -| Choose a save location | Pick a directory to save a file in | ❌ | ✔️ | ✔️ | ✔️ | ❌ | -| Choose a directory | Pick a folder and get its path | ❌ | ✔️ | ✔️ | ✔️ | ❌ | +| Feature | Description | Android | iOS | Linux | macOS | Windows | Web | +| ---------------------- |----------------------------------- |---------|--------- | ---------- | -------- | ------------ | ----------- | +| Choose a single file | Pick a file/image | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | +| Choose multiple files | Pick multiple files/images | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | +| Choose a save location | Pick a directory to save a file in | ❌ | ❌ | ✔️ | ✔️ | ✔️ | ❌ | +| Choose a directory | Pick a directory and get its path | ✔️† | ❌ | ✔️ | ✔️ | ✔️ | ❌ | + +† Choosing a directory is no supported on versions of Android before SDK 21 (Lollipop). [example]:./example [entitlement]: https://docs.flutter.dev/desktop#entitlements-and-the-app-sandbox diff --git a/packages/file_selector/file_selector/example/android/.gitignore b/packages/file_selector/file_selector/example/android/.gitignore new file mode 100644 index 00000000000..6f568019d3c --- /dev/null +++ b/packages/file_selector/file_selector/example/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/packages/file_selector/file_selector/example/android/app/build.gradle b/packages/file_selector/file_selector/example/android/app/build.gradle new file mode 100644 index 00000000000..56af9bc1f95 --- /dev/null +++ b/packages/file_selector/file_selector/example/android/app/build.gradle @@ -0,0 +1,71 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + namespace "dev.flutter.plugins.file_selector_example" + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + applicationId "dev.flutter.plugins.file_selector_example" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion 19 + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/packages/file_selector/file_selector/example/android/app/src/debug/AndroidManifest.xml b/packages/file_selector/file_selector/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000000..399f6981d5d --- /dev/null +++ b/packages/file_selector/file_selector/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/file_selector/file_selector/example/android/app/src/main/AndroidManifest.xml b/packages/file_selector/file_selector/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..2aa121a6d82 --- /dev/null +++ b/packages/file_selector/file_selector/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/packages/file_selector/file_selector/example/android/app/src/main/kotlin/dev/flutter/plugins/file_selector_example/MainActivity.kt b/packages/file_selector/file_selector/example/android/app/src/main/kotlin/dev/flutter/plugins/file_selector_example/MainActivity.kt new file mode 100644 index 00000000000..9bf2bbdb810 --- /dev/null +++ b/packages/file_selector/file_selector/example/android/app/src/main/kotlin/dev/flutter/plugins/file_selector_example/MainActivity.kt @@ -0,0 +1,10 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package dev.flutter.plugins.file_selector_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/packages/file_selector/file_selector/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/file_selector/file_selector/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000000..f74085f3f6a --- /dev/null +++ b/packages/file_selector/file_selector/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/file_selector/file_selector/example/android/app/src/main/res/drawable/launch_background.xml b/packages/file_selector/file_selector/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000000..304732f8842 --- /dev/null +++ b/packages/file_selector/file_selector/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/file_selector/file_selector/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/file_selector/file_selector/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000000..db77bb4b7b0 Binary files /dev/null and b/packages/file_selector/file_selector/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/file_selector/file_selector/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/file_selector/file_selector/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000000..17987b79bb8 Binary files /dev/null and b/packages/file_selector/file_selector/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/file_selector/file_selector/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/file_selector/file_selector/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000000..09d4391482b Binary files /dev/null and b/packages/file_selector/file_selector/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/file_selector/file_selector/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/file_selector/file_selector/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000000..d5f1c8d34e7 Binary files /dev/null and b/packages/file_selector/file_selector/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/file_selector/file_selector/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/file_selector/file_selector/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000000..4d6372eebdb Binary files /dev/null and b/packages/file_selector/file_selector/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/file_selector/file_selector/example/android/app/src/main/res/values-night/styles.xml b/packages/file_selector/file_selector/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000000..06952be745f --- /dev/null +++ b/packages/file_selector/file_selector/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/file_selector/file_selector/example/android/app/src/main/res/values/styles.xml b/packages/file_selector/file_selector/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000000..cb1ef88056e --- /dev/null +++ b/packages/file_selector/file_selector/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/file_selector/file_selector/example/android/app/src/profile/AndroidManifest.xml b/packages/file_selector/file_selector/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000000..399f6981d5d --- /dev/null +++ b/packages/file_selector/file_selector/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/file_selector/file_selector/example/android/build.gradle b/packages/file_selector/file_selector/example/android/build.gradle new file mode 100644 index 00000000000..f7eb7f63ce1 --- /dev/null +++ b/packages/file_selector/file_selector/example/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.3.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/packages/file_selector/file_selector/example/android/gradle.properties b/packages/file_selector/file_selector/example/android/gradle.properties new file mode 100644 index 00000000000..94adc3a3f97 --- /dev/null +++ b/packages/file_selector/file_selector/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/file_selector/file_selector/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/file_selector/file_selector/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..3c472b99c6f --- /dev/null +++ b/packages/file_selector/file_selector/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/packages/file_selector/file_selector/example/android/settings.gradle b/packages/file_selector/file_selector/example/android/settings.gradle new file mode 100644 index 00000000000..44e62bcf06a --- /dev/null +++ b/packages/file_selector/file_selector/example/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/packages/file_selector/file_selector/example/lib/get_directory_page.dart b/packages/file_selector/file_selector/example/lib/get_directory_page.dart index 9236a330d04..3619cb0dac5 100644 --- a/packages/file_selector/file_selector/example/lib/get_directory_page.dart +++ b/packages/file_selector/file_selector/example/lib/get_directory_page.dart @@ -50,7 +50,7 @@ class GetDirectoryPage extends StatelessWidget { ), onPressed: _isIOS ? null : () => _getDirectoryPath(context), child: const Text( - 'Press to ask user to choose a directory (not supported on iOS).', + 'Press to ask user to choose a directory.', ), ), ], diff --git a/packages/file_selector/file_selector/example/lib/home_page.dart b/packages/file_selector/file_selector/example/lib/home_page.dart index 052dba04333..25db9eaba40 100644 --- a/packages/file_selector/file_selector/example/lib/home_page.dart +++ b/packages/file_selector/file_selector/example/lib/home_page.dart @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:io' show Platform; + +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; /// Home Page of the application @@ -43,25 +46,33 @@ class HomePage extends StatelessWidget { child: const Text('Open multiple images'), onPressed: () => Navigator.pushNamed(context, '/open/images'), ), - const SizedBox(height: 10), - ElevatedButton( - style: style, - child: const Text('Save a file'), - onPressed: () => Navigator.pushNamed(context, '/save/text'), - ), - const SizedBox(height: 10), - ElevatedButton( - style: style, - child: const Text('Open a get directory dialog'), - onPressed: () => Navigator.pushNamed(context, '/directory'), - ), - const SizedBox(height: 10), - ElevatedButton( - style: style, - child: const Text('Open a get multi directories dialog'), - onPressed: () => - Navigator.pushNamed(context, '/multi-directories'), - ), + // TODO(stuartmorgan): Replace these checks with support queries once + // https://github.com/flutter/flutter/issues/127328 is implemented. + if (kIsWeb || !(Platform.isAndroid || Platform.isIOS)) ...[ + const SizedBox(height: 10), + ElevatedButton( + style: style, + child: const Text('Save a file'), + onPressed: () => Navigator.pushNamed(context, '/save/text'), + ), + ], + if (!(kIsWeb || Platform.isIOS)) ...[ + const SizedBox(height: 10), + ElevatedButton( + style: style, + child: const Text('Open a get directory dialog'), + onPressed: () => Navigator.pushNamed(context, '/directory'), + ), + ], + if (!(kIsWeb || Platform.isAndroid || Platform.isIOS)) ...[ + const SizedBox(height: 10), + ElevatedButton( + style: style, + child: const Text('Open a get multi directories dialog'), + onPressed: () => + Navigator.pushNamed(context, '/multi-directories'), + ), + ], ], ), ), diff --git a/packages/file_selector/file_selector/example/lib/open_text_page.dart b/packages/file_selector/file_selector/example/lib/open_text_page.dart index 5121e9d55a4..5223e6ab600 100644 --- a/packages/file_selector/file_selector/example/lib/open_text_page.dart +++ b/packages/file_selector/file_selector/example/lib/open_text_page.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:file_selector/file_selector.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; @@ -19,8 +20,8 @@ class OpenTextPage extends StatelessWidget { // This demonstrates using an initial directory for the prompt, which should // only be done in cases where the application can likely predict where the // file would be. In most cases, this parameter should not be provided. - final String initialDirectory = - (await getApplicationDocumentsDirectory()).path; + final String? initialDirectory = + kIsWeb ? null : (await getApplicationDocumentsDirectory()).path; final XFile? file = await openFile( acceptedTypeGroups: [typeGroup], initialDirectory: initialDirectory, diff --git a/packages/file_selector/file_selector/example/lib/save_text_page.dart b/packages/file_selector/file_selector/example/lib/save_text_page.dart index e782530914e..5099e3252bb 100644 --- a/packages/file_selector/file_selector/example/lib/save_text_page.dart +++ b/packages/file_selector/file_selector/example/lib/save_text_page.dart @@ -85,7 +85,7 @@ class SaveTextPage extends StatelessWidget { ), onPressed: _isIOS ? null : () => _saveFile(), child: const Text( - 'Press to save a text file (not supported on iOS).', + 'Press to save a text file.', ), ), ], diff --git a/packages/file_selector/file_selector/pubspec.yaml b/packages/file_selector/file_selector/pubspec.yaml index 6b32deecc33..36cb7a69816 100644 --- a/packages/file_selector/file_selector/pubspec.yaml +++ b/packages/file_selector/file_selector/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for opening and saving files, or selecting directories, using native file selection UI. repository: https://github.com/flutter/packages/tree/main/packages/file_selector/file_selector issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22 -version: 0.9.4 +version: 0.9.5 environment: sdk: ">=2.18.0 <4.0.0" @@ -12,6 +12,8 @@ environment: flutter: plugin: platforms: + android: + default_package: file_selector_android ios: default_package: file_selector_ios linux: @@ -24,6 +26,7 @@ flutter: default_package: file_selector_windows dependencies: + file_selector_android: ^0.5.0 file_selector_ios: ^0.5.0 file_selector_linux: ^0.9.2 file_selector_macos: ^0.9.3 diff --git a/script/configs/exclude_integration_android.yaml b/script/configs/exclude_integration_android.yaml index fe51488c706..64193b5e60c 100644 --- a/script/configs/exclude_integration_android.yaml +++ b/script/configs/exclude_integration_android.yaml @@ -1 +1,2 @@ -[] +# Can't use Flutter integration tests due to native modal UI. +- file_selector