upgraded flutter to v2.5.1

This commit is contained in:
Thibault Deckers 2021-09-23 23:28:16 +09:00
parent ea6f5d7df6
commit e44e74d315
21 changed files with 167 additions and 106 deletions

View file

@ -15,7 +15,7 @@ jobs:
- uses: subosito/flutter-action@v1
with:
channel: stable
flutter-version: '2.5.0'
flutter-version: '2.5.1'
- name: Clone the repository.
uses: actions/checkout@v2

View file

@ -17,7 +17,7 @@ jobs:
- uses: subosito/flutter-action@v1
with:
channel: stable
flutter-version: '2.5.0'
flutter-version: '2.5.1'
# Workaround for this Android Gradle Plugin issue (supposedly fixed in AGP 4.1):
# https://issuetracker.google.com/issues/144111441
@ -50,8 +50,8 @@ jobs:
echo "${{ secrets.KEY_JKS }}" > release.keystore.asc
gpg -d --passphrase "${{ secrets.KEY_JKS_PASSPHRASE }}" --batch release.keystore.asc > $AVES_STORE_FILE
rm release.keystore.asc
flutter build apk --bundle-sksl-path shaders_2.5.0.sksl.json
flutter build appbundle --bundle-sksl-path shaders_2.5.0.sksl.json
flutter build apk --bundle-sksl-path shaders_2.5.1.sksl.json
flutter build appbundle --bundle-sksl-path shaders_2.5.1.sksl.json
rm $AVES_STORE_FILE
env:
AVES_STORE_FILE: ${{ github.workspace }}/key.jks

View file

@ -3,7 +3,7 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
### Changed
- upgraded Flutter to stable v2.5.0
- upgraded Flutter to stable v2.5.1
- faster collection loading when launching the app
## [v1.5.1] - 2021-09-08

View file

@ -1,6 +1,8 @@
package deckers.thibault.aves.channel.calls
import android.app.Activity
import android.media.MediaScannerConnection
import android.net.Uri
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.model.provider.MediaStoreImageProvider
import io.flutter.plugin.common.MethodCall
@ -15,6 +17,7 @@ class MediaStoreHandler(private val activity: Activity) : MethodCallHandler {
when (call.method) {
"checkObsoleteContentIds" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::checkObsoleteContentIds) }
"checkObsoletePaths" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::checkObsoletePaths) }
"scanFile" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::scanFile) }
else -> result.notImplemented()
}
}
@ -37,6 +40,12 @@ class MediaStoreHandler(private val activity: Activity) : MethodCallHandler {
result.success(MediaStoreImageProvider().checkObsoletePaths(activity, knownPathById))
}
private fun scanFile(call: MethodCall, result: MethodChannel.Result) {
val path = call.argument<String>("path")
val mimeType = call.argument<String>("mimeType")
MediaScannerConnection.scanFile(activity, arrayOf(path), arrayOf(mimeType)) { _, uri: Uri? -> result.success(uri?.toString()) }
}
companion object {
const val CHANNEL = "deckers.thibault/aves/media_store"
}

View file

@ -1,8 +1,6 @@
package deckers.thibault.aves.channel.calls
import android.content.Context
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.os.storage.StorageManager
@ -30,7 +28,6 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
"getRestrictedDirectories" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::getRestrictedDirectories) }
"revokeDirectoryAccess" -> safe(call, result, ::revokeDirectoryAccess)
"deleteEmptyDirectories" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::deleteEmptyDirectories) }
"scanFile" -> GlobalScope.launch(Dispatchers.IO) { safe(call, result, ::scanFile) }
else -> result.notImplemented()
}
}
@ -158,12 +155,6 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
result.success(deleted)
}
private fun scanFile(call: MethodCall, result: MethodChannel.Result) {
val path = call.argument<String>("path")
val mimeType = call.argument<String>("mimeType")
MediaScannerConnection.scanFile(context, arrayOf(path), arrayOf(mimeType)) { _, uri: Uri? -> result.success(uri?.toString()) }
}
companion object {
const val CHANNEL = "deckers.thibault/aves/storage"
}

View file

@ -64,9 +64,9 @@ object MimeTypes {
else -> false
}
// as of Flutter v1.22.0
// as of Flutter v1.22.0, with additional custom handling for SVG
fun canDecodeWithFlutter(mimeType: String, rotationDegrees: Int?, isFlipped: Boolean?) = when (mimeType) {
JPEG, GIF, WEBP, BMP, WBMP, ICO -> true
JPEG, GIF, WEBP, BMP, WBMP, ICO, SVG -> true
PNG -> rotationDegrees ?: 0 == 0 && !(isFlipped ?: false)
else -> false
}

View file

@ -13,7 +13,6 @@ import 'package:aves/model/settings/screen_on.dart';
import 'package:aves/model/source/enums.dart';
import 'package:aves/services/common/services.dart';
import 'package:collection/collection.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';
@ -111,13 +110,6 @@ class Settings extends ChangeNotifier {
_isRotationLocked = await windowService.isRotationLocked();
}
// Crashlytics initialization is separated from the main settings initialization
// to allow settings customization without Firebase context (e.g. before a Flutter Driver test)
Future<void> initFirebase() async {
await Firebase.app().setAutomaticDataCollectionEnabled(isCrashlyticsEnabled);
await reportService.setCollectionEnabled(isCrashlyticsEnabled);
}
Future<void> reset({required bool includeInternalKeys}) async {
if (includeInternalKeys) {
await _prefs!.clear();
@ -151,7 +143,7 @@ class Settings extends ChangeNotifier {
set isCrashlyticsEnabled(bool newValue) {
setAndNotify(isCrashlyticsEnabledKey, newValue);
unawaited(initFirebase());
unawaited(reportService.setCollectionEnabled(isCrashlyticsEnabled));
}
static const localeSeparator = '-';

View file

@ -1,4 +1,5 @@
import 'package:collection/collection.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
@ -27,7 +28,10 @@ class CrashlyticsReportService extends ReportService {
bool get isCollectionEnabled => instance.isCrashlyticsCollectionEnabled;
@override
Future<void> setCollectionEnabled(bool enabled) => instance.setCrashlyticsCollectionEnabled(enabled);
Future<void> setCollectionEnabled(bool enabled) async {
await Firebase.app().setAutomaticDataCollectionEnabled(enabled);
await instance.setCrashlyticsCollectionEnabled(enabled);
}
@override
Future<void> log(String message) => instance.log(message);

View file

@ -14,18 +14,18 @@ abstract class StorageService {
Future<List<String>> getGrantedDirectories();
Future<void> revokeDirectoryAccess(String path);
Future<Set<VolumeRelativeDirectory>> getInaccessibleDirectories(Iterable<String> dirPaths);
Future<Set<VolumeRelativeDirectory>> getRestrictedDirectories();
// returns whether user granted access to volume root at `volumePath`
Future<bool> requestVolumeAccess(String volumePath);
Future<void> revokeDirectoryAccess(String path);
// returns number of deleted directories
Future<int> deleteEmptyDirectories(Iterable<String> dirPaths);
// returns whether user granted access to volume root at `volumePath`
Future<bool> requestVolumeAccess(String volumePath);
// return whether operation succeeded (`null` if user cancelled)
Future<bool?> createFile(String name, String mimeType, Uint8List bytes);
@ -73,18 +73,6 @@ class PlatformStorageService implements StorageService {
return [];
}
@override
Future<void> revokeDirectoryAccess(String path) async {
try {
await platform.invokeMethod('revokeDirectoryAccess', <String, dynamic>{
'path': path,
});
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
}
return;
}
@override
Future<Set<VolumeRelativeDirectory>> getInaccessibleDirectories(Iterable<String> dirPaths) async {
try {
@ -113,6 +101,32 @@ class PlatformStorageService implements StorageService {
return {};
}
@override
Future<void> revokeDirectoryAccess(String path) async {
try {
await platform.invokeMethod('revokeDirectoryAccess', <String, dynamic>{
'path': path,
});
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
}
return;
}
// returns number of deleted directories
@override
Future<int> deleteEmptyDirectories(Iterable<String> dirPaths) async {
try {
final result = await platform.invokeMethod('deleteEmptyDirectories', <String, dynamic>{
'dirPaths': dirPaths.toList(),
});
if (result != null) return result as int;
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
}
return 0;
}
// returns whether user granted access to volume root at `volumePath`
@override
Future<bool> requestVolumeAccess(String volumePath) async {
@ -136,20 +150,6 @@ class PlatformStorageService implements StorageService {
return false;
}
// returns number of deleted directories
@override
Future<int> deleteEmptyDirectories(Iterable<String> dirPaths) async {
try {
final result = await platform.invokeMethod('deleteEmptyDirectories', <String, dynamic>{
'dirPaths': dirPaths.toList(),
});
if (result != null) return result as int;
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
}
return 0;
}
@override
Future<bool?> createFile(String name, String mimeType, Uint8List bytes) async {
try {

View file

@ -139,7 +139,7 @@ class _AvesAppState extends State<AvesApp> {
});
});
await settings.init();
await settings.initFirebase();
await reportService.setCollectionEnabled(settings.isCrashlyticsEnabled);
_navigatorObservers = [
CrashlyticsRouteTracker(),
];

View file

@ -154,7 +154,7 @@ class _EntryLeafletMapState extends State<EntryLeafletMap> with TickerProviderSt
minZoom: widget.minZoom,
maxZoom: widget.maxZoom,
interactiveFlags: interactive ? InteractiveFlag.all : InteractiveFlag.none,
onTap: (point) => widget.onMapTap?.call(),
onTap: (tapPosition, point) => widget.onMapTap?.call(),
controller: _leafletMapController,
),
mapController: _leafletMapController,

View file

@ -7,14 +7,14 @@ packages:
name: _fe_analyzer_shared
url: "https://pub.dartlang.org"
source: hosted
version: "25.0.0"
version: "26.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.0"
version: "2.3.0"
archive:
dependency: transitive
description:
@ -28,7 +28,7 @@ packages:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.0"
version: "2.3.0"
async:
dependency: transitive
description:
@ -378,7 +378,7 @@ packages:
name: flutter_map
url: "https://pub.dartlang.org"
source: hosted
version: "0.13.1"
version: "0.14.0"
flutter_markdown:
dependency: "direct main"
description:
@ -456,7 +456,7 @@ packages:
name: google_maps_flutter
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.8"
version: "2.0.10"
google_maps_flutter_platform_interface:
dependency: transitive
description:
@ -498,7 +498,7 @@ packages:
name: image
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.2"
version: "3.0.5"
intl:
dependency: "direct main"
description:
@ -554,7 +554,7 @@ packages:
name: logging
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
version: "1.0.2"
markdown:
dependency: transitive
description:
@ -638,7 +638,7 @@ packages:
name: package_config
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.0.2"
package_info_plus:
dependency: "direct main"
description:
@ -715,7 +715,7 @@ packages:
name: path_provider_linux
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
version: "2.1.0"
path_provider_platform_interface:
dependency: transitive
description:
@ -736,7 +736,7 @@ packages:
name: pdf
url: "https://pub.dartlang.org"
source: hosted
version: "3.5.0"
version: "3.6.0"
pedantic:
dependency: transitive
description:
@ -757,7 +757,7 @@ packages:
name: permission_handler
url: "https://pub.dartlang.org"
source: hosted
version: "8.1.4+2"
version: "8.1.6"
permission_handler_platform_interface:
dependency: transitive
description:
@ -771,7 +771,7 @@ packages:
name: petitparser
url: "https://pub.dartlang.org"
source: hosted
version: "4.2.0"
version: "4.3.0"
platform:
dependency: transitive
description:
@ -806,7 +806,7 @@ packages:
name: printing
url: "https://pub.dartlang.org"
source: hosted
version: "5.5.0"
version: "5.6.0"
process:
dependency: transitive
description:
@ -834,7 +834,7 @@ packages:
name: pub_semver
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.1.0"
qr:
dependency: transitive
description:
@ -855,7 +855,7 @@ packages:
name: shared_preferences
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.7"
version: "2.0.8"
shared_preferences_linux:
dependency: transitive
description:
@ -1072,7 +1072,7 @@ packages:
name: url_launcher
url: "https://pub.dartlang.org"
source: hosted
version: "6.0.10"
version: "6.0.11"
url_launcher_linux:
dependency: transitive
description:
@ -1184,7 +1184,7 @@ packages:
name: xml
url: "https://pub.dartlang.org"
source: hosted
version: "5.2.0"
version: "5.3.0"
yaml:
dependency: transitive
description:
@ -1194,4 +1194,4 @@ packages:
version: "3.1.0"
sdks:
dart: ">=2.14.0 <3.0.0"
flutter: ">=2.0.0"
flutter: ">=2.5.0"

View file

@ -12,7 +12,7 @@ dependencies:
sdk: flutter
flutter_localizations:
sdk: flutter
# TODO TLAD as of 2021/09/09, the released version is incompatible with Flutter v2.5
# TODO TLAD as of 2021/09/23, latest version (v0.11.0) is incompatible with Flutter v2.5
charts_flutter:
git:
url: git://github.com/google/charts.git
@ -20,7 +20,7 @@ dependencies:
collection:
connectivity_plus:
country_code:
# TODO TLAD as of 2021/08/04, null safe version is pre-release
# TODO TLAD as of 2021/09/23, null safe version is pre-release
custom_rounded_rectangle_border: '>=0.2.0-nullsafety.0'
decorated_icon:
device_info_plus:
@ -47,12 +47,12 @@ dependencies:
google_maps_flutter:
intl:
latlong2:
# TODO TLAD as of 2021/08/04, null safe version is pre-release
# TODO TLAD as of 2021/09/23, null safe version is pre-release
material_design_icons_flutter: '>=5.0.5955-rc.1'
overlay_support:
package_info_plus:
palette_generator:
# TODO TLAD upgrade panorama when this is fixed: https://github.com/zesage/panorama/issues/25 (bug in v0.4.1)
# TODO TLAD as of 2021/09/23, latest version (v0.4.1) has this issue: https://github.com/zesage/panorama/issues/25
panorama: 0.4.0
pdf:
percent_indicator:

1
shaders.sksl.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
shaders_2.5.1.sksl.json Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,26 @@
import 'package:aves/services/report_service.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
class FakeReportService extends ReportService {
@override
bool get isCollectionEnabled => false;
@override
Future<void> setCollectionEnabled(bool enabled) => SynchronousFuture(null);
@override
Future<void> log(String message) => SynchronousFuture(null);
@override
Future<void> setCustomKey(String key, Object value) => SynchronousFuture(null);
@override
Future<void> setCustomKeys(Map<String, Object> map) => SynchronousFuture(null);
@override
Future<void> recordError(exception, StackTrace? stack) => SynchronousFuture(null);
@override
Future<void> recordFlutterError(FlutterErrorDetails flutterErrorDetails) => SynchronousFuture(null);
}

View file

@ -1,8 +1,21 @@
import 'package:aves/services/window_service.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
class FakeWindowService extends Fake implements WindowService {
@override
Future<void> keepScreenOn(bool on) => SynchronousFuture(null);
@override
Future<bool> isRotationLocked() => SynchronousFuture(false);
@override
Future<void> requestOrientation([Orientation? orientation]) => SynchronousFuture(null);
@override
Future<bool> canSetCutoutMode() => SynchronousFuture(true);
@override
Future<void> setCutoutMode(bool use) => SynchronousFuture(null);
}

View file

@ -13,6 +13,7 @@ import 'package:aves/services/device_service.dart';
import 'package:aves/services/media/media_file_service.dart';
import 'package:aves/services/media/media_store_service.dart';
import 'package:aves/services/metadata/metadata_fetch_service.dart';
import 'package:aves/services/report_service.dart';
import 'package:aves/services/storage_service.dart';
import 'package:aves/services/window_service.dart';
import 'package:aves/utils/android_file_utils.dart';
@ -26,6 +27,7 @@ import '../fake/media_file_service.dart';
import '../fake/media_store_service.dart';
import '../fake/metadata_db.dart';
import '../fake/metadata_fetch_service.dart';
import '../fake/report_service.dart';
import '../fake/storage_service.dart';
import '../fake/window_service.dart';
@ -44,6 +46,7 @@ void main() {
getIt.registerLazySingleton<MediaFileService>(() => FakeMediaFileService());
getIt.registerLazySingleton<MediaStoreService>(() => FakeMediaStoreService());
getIt.registerLazySingleton<MetadataFetchService>(() => FakeMetadataFetchService());
getIt.registerLazySingleton<ReportService>(() => FakeReportService());
getIt.registerLazySingleton<StorageService>(() => FakeStorageService());
getIt.registerLazySingleton<WindowService>(() => FakeWindowService());

View file

@ -5,10 +5,11 @@ import 'package:aves/model/settings/enums.dart';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/services/media/media_store_service.dart';
import 'package:aves/services/report_service.dart';
import 'package:aves/services/window_service.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/src/widgets/media_query.dart';
import 'package:flutter_driver/driver_extension.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:path/path.dart' as p;
import 'constants.dart';
@ -19,45 +20,65 @@ void main() {
// scan files copied from test assets
// we do it via the app instead of broadcasting via ADB
// because `MEDIA_SCANNER_SCAN_FILE` intent got deprecated in API 29
final mediaStoreService = PlatformMediaStoreService();
mediaStoreService.scanFile(p.join(targetPicturesDir, 'aves_logo.svg'), 'image/svg+xml');
mediaStoreService.scanFile(p.join(targetPicturesDir, 'ipse.jpg'), 'image/jpeg');
PlatformMediaStoreService()
..scanFile(p.join(targetPicturesDir, 'aves_logo.svg'), 'image/svg+xml')
..scanFile(p.join(targetPicturesDir, 'ipse.jpg'), 'image/jpeg');
// something like `configure().then((_) => app.main());` does not behave as expected
// and starts the app without waiting for `configure` to complete
configureAndLaunch();
}
Future<void> configureAndLaunch() async {
// TODO TLAD [test] decouple services from settings setters, so there is no need for fake services here
// set up fake services called during settings initialization
final fakeWindowService = FakeWindowService();
getIt.registerSingleton<WindowService>(fakeWindowService);
getIt
..registerSingleton<WindowService>(DriverInitWindowService())
..registerSingleton<ReportService>(DriverInitReportService());
await settings.init();
settings.keepScreenOn = KeepScreenOn.always;
settings.hasAcceptedTerms = false;
settings.isCrashlyticsEnabled = false;
settings.locale = const Locale('en');
settings.homePage = HomePageSetting.collection;
settings.imageBackground = EntryBackground.checkered;
settings
..keepScreenOn = KeepScreenOn.always
..hasAcceptedTerms = false
..isCrashlyticsEnabled = false
..locale = const Locale('en')
..homePage = HomePageSetting.collection
..imageBackground = EntryBackground.checkered;
// tear down fake services
getIt.unregister<WindowService>(instance: fakeWindowService);
await Future.delayed(const Duration(seconds: 1));
await getIt.reset();
app.main();
}
class FakeWindowService implements WindowService {
class DriverInitWindowService extends Fake implements WindowService {
@override
Future<void> keepScreenOn(bool on) => SynchronousFuture(null);
@override
Future<bool> isRotationLocked() => SynchronousFuture(false);
@override
Future<void> requestOrientation([Orientation? orientation]) => SynchronousFuture(null);
@override
Future<bool> canSetCutoutMode() => SynchronousFuture(false);
@override
Future<void> setCutoutMode(bool use) => SynchronousFuture(null);
}
class DriverInitReportService extends Fake implements ReportService {
@override
bool get isCollectionEnabled => false;
@override
Future<void> setCollectionEnabled(bool enabled) => SynchronousFuture(null);
@override
Future<void> log(String message) => SynchronousFuture(null);
@override
Future<void> setCustomKey(String key, Object value) => SynchronousFuture(null);
@override
Future<void> setCustomKeys(Map<String, Object> map) => SynchronousFuture(null);
@override
Future<void> recordError(exception, StackTrace? stack) => SynchronousFuture(null);
@override
Future<void> recordFlutterError(FlutterErrorDetails flutterErrorDetails) => SynchronousFuture(null);
}

View file

@ -14,8 +14,6 @@ late FlutterDriver driver;
void main() {
group('[Aves app]', () {
print('adb=${[adb, ...adbDeviceParam].join(' ')}');
setUpAll(() async {
await copyContent(sourcePicturesDir, targetPicturesDir);
await grantPermissions('deckers.thibault.aves.debug', [
@ -53,6 +51,9 @@ void main() {
void agreeToTerms() {
test('[welcome] agree to terms', () async {
// delay to avoid flaky failures when widget binding is not ready from the start
await Future.delayed(const Duration(seconds: 3));
await driver.scroll(find.text('Terms of Service'), 0, -300, const Duration(milliseconds: 500));
await driver.tap(find.byValueKey('agree-checkbox'));