[file_selector] Endorse Android (#4329)

Endorses the new Android implementation of `file_selector`, updating the README accordingly.

Also adjusts the example to better handle platform diffs, by hiding UI that doesn't work instead of just labelling it on the button. Left a TODO to improve how we determine what to hide once we have the API to do so.

Fixes https://github.com/flutter/flutter/issues/110098
This commit is contained in:
stuartmorgan
2023-07-01 06:53:14 -04:00
committed by GitHub
parent 6ab9a8bfb4
commit 41d5ca918d
29 changed files with 325 additions and 40 deletions

View File

@ -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:

View File

@ -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

View File

@ -1,3 +1,7 @@
## 0.9.5
* Adds an endorsed Android implementation.
## 0.9.4
* Adds `getSaveLocation` and deprecates `getSavePath`.

View File

@ -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

View File

@ -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

View File

@ -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"
}

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -0,0 +1,33 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="file_selector_example"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>

View File

@ -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() {
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -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
}

View File

@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true

View File

@ -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

View File

@ -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"

View File

@ -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.',
),
),
],

View File

@ -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)) ...<Widget>[
const SizedBox(height: 10),
ElevatedButton(
style: style,
child: const Text('Save a file'),
onPressed: () => Navigator.pushNamed(context, '/save/text'),
),
],
if (!(kIsWeb || Platform.isIOS)) ...<Widget>[
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)) ...<Widget>[
const SizedBox(height: 10),
ElevatedButton(
style: style,
child: const Text('Open a get multi directories dialog'),
onPressed: () =>
Navigator.pushNamed(context, '/multi-directories'),
),
],
],
),
),

View File

@ -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: <XTypeGroup>[typeGroup],
initialDirectory: initialDirectory,

View File

@ -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.',
),
),
],

View File

@ -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

View File

@ -1 +1,2 @@
[]
# Can't use Flutter integration tests due to native modal UI.
- file_selector