Perf improvements (#1097)

This commit is contained in:
Ashil 2023-05-16 18:12:26 +05:30 committed by GitHub
commit 47a7ac3bb2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 277 additions and 49 deletions

View file

@ -0,0 +1,122 @@
import "package:flutter/material.dart";
import "package:flutter_test/flutter_test.dart";
import "package:integration_test/integration_test.dart";
import "package:photos/main.dart" as app;
import "package:scrollable_positioned_list/scrollable_positioned_list.dart";
void main() {
group("App test", () {
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive;
testWidgets("Demo test", (tester) async {
app.main();
await tester.pumpAndSettle(const Duration(seconds: 5));
await dismissUpdateAppDialog(tester);
//Automatically clicks the sign in button on the landing page
final signInButton = find.byKey(const ValueKey("signInButton"));
await tester.tap(signInButton);
await tester.pumpAndSettle();
//Need to enter email address manually and clicks the login button automatically
final emailInputField = find.byKey(const ValueKey("emailInputField"));
final logInButton = find.byKey(const ValueKey("logInButton"));
await tester.tap(emailInputField);
await tester.pumpAndSettle(const Duration(seconds: 20));
await findAndTapFAB(tester, logInButton);
//Need to enter OTT manually and clicks the verify button automatically
final ottVerificationInputField =
find.byKey(const ValueKey("ottVerificationInputField"));
final verifyOttButton = find.byKey(const ValueKey("verifyOttButton"));
await tester.tap(ottVerificationInputField);
await tester.pumpAndSettle(const Duration(seconds: 6));
await findAndTapFAB(tester, verifyOttButton);
//Need to enter password manually and clicks the verify button automatically
final passwordInputField =
find.byKey(const ValueKey("passwordInputField"));
final verifyPasswordButton =
find.byKey(const ValueKey("verifyPasswordButton"));
await tester.tap(passwordInputField);
await tester.pumpAndSettle(const Duration(seconds: 10));
await findAndTapFAB(tester, verifyPasswordButton);
await tester.pumpAndSettle(const Duration(seconds: 1));
await dismissUpdateAppDialog(tester);
//Grant permission to access photos. Must manually click the system dialog.
final grantPermissionButton =
find.byKey(const ValueKey("grantPermissionButton"));
await tester.tap(grantPermissionButton);
await tester.pumpAndSettle(const Duration(seconds: 1));
await tester.pumpAndSettle(const Duration(seconds: 3));
//Automatically skips backup
final skipBackupButton = find.byKey(const ValueKey("skipBackupButton"));
await tester.tap(skipBackupButton);
await tester.pumpAndSettle(const Duration(seconds: 2));
await binding.traceAction(
() async {
//scroll gallery
final scrollablePositionedList =
find.byType(ScrollablePositionedList);
await tester.fling(
scrollablePositionedList,
const Offset(0, -5000),
4500,
);
await tester.pumpAndSettle();
await tester.fling(
scrollablePositionedList,
const Offset(0, 5000),
4500,
);
await tester.fling(
scrollablePositionedList,
const Offset(0, -7000),
4500,
);
await tester.pumpAndSettle();
await tester.fling(
scrollablePositionedList,
const Offset(0, 7000),
4500,
);
await tester.fling(
scrollablePositionedList,
const Offset(0, -9000),
4500,
);
await tester.pumpAndSettle();
await tester.fling(
scrollablePositionedList,
const Offset(0, 9000),
4500,
);
await tester.pumpAndSettle();
},
reportKey: 'scrolling_summary',
);
});
});
}
Future<void> findAndTapFAB(WidgetTester tester, Finder finder) async {
final RenderBox box = tester.renderObject(finder);
final Offset desiredOffset = Offset(box.size.width - 10, box.size.height / 2);
// Calculate the global position of the desired offset within the widget.
final Offset globalPosition = box.localToGlobal(desiredOffset);
await tester.tapAt(globalPosition);
await tester.pumpAndSettle(const Duration(seconds: 3));
}
Future<void> dismissUpdateAppDialog(WidgetTester tester) async {
await tester.tapAt(const Offset(0, 0));
await tester.pumpAndSettle();
}

View file

@ -99,6 +99,8 @@ PODS:
- in_app_purchase_storekit (0.0.1): - in_app_purchase_storekit (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- integration_test (0.0.1):
- Flutter
- libwebp (1.2.4): - libwebp (1.2.4):
- libwebp/demux (= 1.2.4) - libwebp/demux (= 1.2.4)
- libwebp/mux (= 1.2.4) - libwebp/mux (= 1.2.4)
@ -195,6 +197,7 @@ DEPENDENCIES:
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- image_editor_common (from `.symlinks/plugins/image_editor_common/ios`) - image_editor_common (from `.symlinks/plugins/image_editor_common/ios`)
- in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/ios`) - in_app_purchase_storekit (from `.symlinks/plugins/in_app_purchase_storekit/ios`)
- integration_test (from `.symlinks/plugins/integration_test/ios`)
- local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`) - local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`)
- media_extension (from `.symlinks/plugins/media_extension/ios`) - media_extension (from `.symlinks/plugins/media_extension/ios`)
- motionphoto (from `.symlinks/plugins/motionphoto/ios`) - motionphoto (from `.symlinks/plugins/motionphoto/ios`)
@ -276,6 +279,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/image_editor_common/ios" :path: ".symlinks/plugins/image_editor_common/ios"
in_app_purchase_storekit: in_app_purchase_storekit:
:path: ".symlinks/plugins/in_app_purchase_storekit/ios" :path: ".symlinks/plugins/in_app_purchase_storekit/ios"
integration_test:
:path: ".symlinks/plugins/integration_test/ios"
local_auth_ios: local_auth_ios:
:path: ".symlinks/plugins/local_auth_ios/ios" :path: ".symlinks/plugins/local_auth_ios/ios"
media_extension: media_extension:
@ -345,6 +350,7 @@ SPEC CHECKSUMS:
GoogleUtilities: c2bdc4cf2ce786c4d2e6b3bcfd599a25ca78f06f GoogleUtilities: c2bdc4cf2ce786c4d2e6b3bcfd599a25ca78f06f
image_editor_common: d6f6644ae4a6de80481e89fe6d0a8c49e30b4b43 image_editor_common: d6f6644ae4a6de80481e89fe6d0a8c49e30b4b43
in_app_purchase_storekit: 6b297e2b5eab9fa3251a492d57301722e4132a71 in_app_purchase_storekit: 6b297e2b5eab9fa3251a492d57301722e4132a71
integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5
libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef
local_auth_ios: c6cf091ded637a88f24f86a8875d8b0f526e2605 local_auth_ios: c6cf091ded637a88f24f86a8875d8b0f526e2605
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d

View file

@ -289,6 +289,7 @@
"${BUILT_PRODUCTS_DIR}/fluttertoast/fluttertoast.framework", "${BUILT_PRODUCTS_DIR}/fluttertoast/fluttertoast.framework",
"${BUILT_PRODUCTS_DIR}/image_editor_common/image_editor_common.framework", "${BUILT_PRODUCTS_DIR}/image_editor_common/image_editor_common.framework",
"${BUILT_PRODUCTS_DIR}/in_app_purchase_storekit/in_app_purchase_storekit.framework", "${BUILT_PRODUCTS_DIR}/in_app_purchase_storekit/in_app_purchase_storekit.framework",
"${BUILT_PRODUCTS_DIR}/integration_test/integration_test.framework",
"${BUILT_PRODUCTS_DIR}/libwebp/libwebp.framework", "${BUILT_PRODUCTS_DIR}/libwebp/libwebp.framework",
"${BUILT_PRODUCTS_DIR}/local_auth_ios/local_auth_ios.framework", "${BUILT_PRODUCTS_DIR}/local_auth_ios/local_auth_ios.framework",
"${BUILT_PRODUCTS_DIR}/media_extension/media_extension.framework", "${BUILT_PRODUCTS_DIR}/media_extension/media_extension.framework",
@ -345,6 +346,7 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/fluttertoast.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/fluttertoast.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_editor_common.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_editor_common.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/in_app_purchase_storekit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/in_app_purchase_storekit.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/integration_test.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libwebp.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libwebp.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/local_auth_ios.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/local_auth_ios.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/media_extension.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/media_extension.framework",

View file

@ -53,6 +53,7 @@ class _LoginPageState extends State<LoginPage> {
), ),
body: _getBody(), body: _getBody(),
floatingActionButton: DynamicFAB( floatingActionButton: DynamicFAB(
key: const ValueKey("logInButton"),
isKeypadOpen: isKeypadOpen, isKeypadOpen: isKeypadOpen,
isFormValid: _emailIsValid, isFormValid: _emailIsValid,
buttonText: S.of(context).logInLabel, buttonText: S.of(context).logInLabel,
@ -87,6 +88,7 @@ class _LoginPageState extends State<LoginPage> {
Padding( Padding(
padding: const EdgeInsets.fromLTRB(20, 24, 20, 0), padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
child: TextFormField( child: TextFormField(
key: const ValueKey("emailInputField"),
autofillHints: const [AutofillHints.email], autofillHints: const [AutofillHints.email],
decoration: InputDecoration( decoration: InputDecoration(
fillColor: _emailInputFieldColor, fillColor: _emailInputFieldColor,
@ -186,9 +188,9 @@ class _LoginPageState extends State<LoginPage> {
}, },
), ),
), ),
Expanded( const Expanded(
flex: 1, flex: 1,
child: Container(), child: SizedBox.shrink(),
) )
], ],
), ),

View file

@ -64,6 +64,7 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
), ),
body: _getBody(), body: _getBody(),
floatingActionButton: DynamicFAB( floatingActionButton: DynamicFAB(
key: const ValueKey("verifyOttButton"),
isKeypadOpen: isKeypadOpen, isKeypadOpen: isKeypadOpen,
isFormValid: _verificationCodeController.text.isNotEmpty, isFormValid: _verificationCodeController.text.isNotEmpty,
buttonText: S.of(context).verify, buttonText: S.of(context).verify,
@ -146,6 +147,7 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
Padding( Padding(
padding: const EdgeInsets.fromLTRB(20, 16, 20, 16), padding: const EdgeInsets.fromLTRB(20, 16, 20, 16),
child: TextFormField( child: TextFormField(
key: const ValueKey("ottVerificationInputField"),
style: Theme.of(context).textTheme.subtitle1, style: Theme.of(context).textTheme.subtitle1,
decoration: InputDecoration( decoration: InputDecoration(
filled: true, filled: true,

View file

@ -66,6 +66,7 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
), ),
body: _getBody(), body: _getBody(),
floatingActionButton: DynamicFAB( floatingActionButton: DynamicFAB(
key: const ValueKey("verifyPasswordButton"),
isKeypadOpen: isKeypadOpen, isKeypadOpen: isKeypadOpen,
isFormValid: _passwordController.text.isNotEmpty, isFormValid: _passwordController.text.isNotEmpty,
buttonText: S.of(context).verifyPassword, buttonText: S.of(context).verifyPassword,
@ -169,6 +170,7 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
Padding( Padding(
padding: const EdgeInsets.fromLTRB(20, 24, 20, 0), padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
child: TextFormField( child: TextFormField(
key: const ValueKey("passwordInputField"),
autofillHints: const [AutofillHints.password], autofillHints: const [AutofillHints.password],
decoration: InputDecoration( decoration: InputDecoration(
hintText: S.of(context).enterYourPassword, hintText: S.of(context).enterYourPassword,

View file

@ -185,6 +185,7 @@ class _BackupFolderSelectionPageState extends State<BackupFolderSelectionPage> {
bottom: Platform.isIOS ? 48 : 32, bottom: Platform.isIOS ? 48 : 32,
), ),
child: GestureDetector( child: GestureDetector(
key: const ValueKey("skipBackupButton"),
onTap: () { onTap: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },

View file

@ -92,6 +92,7 @@ class GrantPermissionsWidget extends StatelessWidget {
bottom: 16, bottom: 16,
), ),
child: OutlinedButton( child: OutlinedButton(
key: const ValueKey("grantPermissionButton"),
child: Text(S.of(context).grantPermission), child: Text(S.of(context).grantPermission),
onPressed: () async { onPressed: () async {
final state = await PhotoManager.requestPermissionExtend(); final state = await PhotoManager.requestPermissionExtend();

View file

@ -116,6 +116,7 @@ class _LandingPageWidgetState extends State<LandingPageWidget> {
child: Hero( child: Hero(
tag: "log_in", tag: "log_in",
child: ElevatedButton( child: ElevatedButton(
key: const ValueKey("signInButton"),
style: style:
Theme.of(context).colorScheme.optionalActionButtonStyle, Theme.of(context).colorScheme.optionalActionButtonStyle,
onPressed: _navigateToSignInPage, onPressed: _navigateToSignInPage,

View file

@ -112,6 +112,7 @@ class _AppUpdateDialogState extends State<AppUpdateDialog> {
return WillPopScope( return WillPopScope(
onWillPop: () async => !shouldForceUpdate, onWillPop: () async => !shouldForceUpdate,
child: AlertDialog( child: AlertDialog(
key: const ValueKey("updateAppDialog"),
title: Column( title: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [

View file

@ -58,10 +58,13 @@ class _ThumbnailWidgetState extends State<ThumbnailWidget> {
bool _isLoadingRemoteThumbnail = false; bool _isLoadingRemoteThumbnail = false;
bool _errorLoadingRemoteThumbnail = false; bool _errorLoadingRemoteThumbnail = false;
ImageProvider? _imageProvider; ImageProvider? _imageProvider;
int? optimizedImageHeight;
int? optimizedImageWidth;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
assignOptimizedImageDimensions();
} }
@override @override
@ -83,6 +86,19 @@ class _ThumbnailWidgetState extends State<ThumbnailWidget> {
} }
} }
///Assigned dimension will be the size of a grid item. The size will be
///assigned to the side which is smaller in dimension.
void assignOptimizedImageDimensions() {
if (widget.file!.width == 0 || widget.file!.height == 0) {
return;
}
if (widget.file!.width < widget.file!.height) {
optimizedImageWidth = widget.thumbnailSize;
} else {
optimizedImageHeight = widget.thumbnailSize;
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (widget.file!.isRemoteFile) { if (widget.file!.isRemoteFile) {
@ -93,7 +109,13 @@ class _ThumbnailWidgetState extends State<ThumbnailWidget> {
Widget? image; Widget? image;
if (_imageProvider != null) { if (_imageProvider != null) {
image = Image( image = Image(
image: _imageProvider!, image: optimizedImageHeight != null || optimizedImageWidth != null
? ResizeImage(
_imageProvider!,
width: optimizedImageWidth,
height: optimizedImageHeight,
)
: _imageProvider!,
fit: widget.fit, fit: widget.fit,
); );
} }
@ -148,11 +170,7 @@ class _ThumbnailWidgetState extends State<ThumbnailWidget> {
} }
final List<Widget> viewChildren = [ final List<Widget> viewChildren = [
const ThumbnailPlaceHolder(), const ThumbnailPlaceHolder(),
AnimatedOpacity( content ?? const SizedBox(),
opacity: content == null ? 0 : 1.0,
duration: const Duration(milliseconds: 200),
child: content,
)
]; ];
if (widget.shouldShowSyncStatus && widget.file!.uploadedFileID == null) { if (widget.shouldShowSyncStatus && widget.file!.uploadedFileID == null) {
viewChildren.add(const UnSyncedIcon()); viewChildren.add(const UnSyncedIcon());

View file

@ -60,46 +60,58 @@ class GalleryFileWidget extends StatelessWidget {
? _onLongPressWithSelectionLimit(context, file) ? _onLongPressWithSelectionLimit(context, file)
: _onLongPressNoSelectionLimit(context, file); : _onLongPressNoSelectionLimit(context, file);
}, },
child: ClipRRect( child: Stack(
borderRadius: BorderRadius.circular(1), children: [
child: Stack( ClipRRect(
children: [ borderRadius: BorderRadius.circular(1),
Hero( child: Hero(
tag: tag + file.tag, tag: tag + file.tag,
child: ColorFiltered( child: isFileSelected
colorFilter: ColorFilter.mode( ? ColorFiltered(
Colors.black.withOpacity( colorFilter: ColorFilter.mode(
isFileSelected ? 0.4 : 0, Colors.black.withOpacity(
), 0.4,
BlendMode.darken, ),
), BlendMode.darken,
child: ThumbnailWidget( ),
file, child: ThumbnailWidget(
diskLoadDeferDuration: thumbnailDiskLoadDeferDuration, file,
serverLoadDeferDuration: thumbnailServerLoadDeferDuration, diskLoadDeferDuration: thumbnailDiskLoadDeferDuration,
shouldShowLivePhotoOverlay: true, serverLoadDeferDuration:
key: Key(tag + file.tag), thumbnailServerLoadDeferDuration,
thumbnailSize: photoGridSize < photoGridSizeDefault shouldShowLivePhotoOverlay: true,
? thumbnailLargeSize key: Key(tag + file.tag),
: thumbnailSmallSize, thumbnailSize: photoGridSize < photoGridSizeDefault
shouldShowOwnerAvatar: !isFileSelected, ? thumbnailLargeSize
), : thumbnailSmallSize,
), shouldShowOwnerAvatar: !isFileSelected,
),
)
: ThumbnailWidget(
file,
diskLoadDeferDuration: thumbnailDiskLoadDeferDuration,
serverLoadDeferDuration: thumbnailServerLoadDeferDuration,
shouldShowLivePhotoOverlay: true,
key: Key(tag + file.tag),
thumbnailSize: photoGridSize < photoGridSizeDefault
? thumbnailLargeSize
: thumbnailSmallSize,
shouldShowOwnerAvatar: !isFileSelected,
),
), ),
Visibility( ),
visible: isFileSelected, isFileSelected
child: Positioned( ? Positioned(
right: 4, right: 4,
top: 4, top: 4,
child: Icon( child: Icon(
Icons.check_circle_rounded, Icons.check_circle_rounded,
size: 20, size: 20,
color: selectionColor, //same for both themes color: selectionColor, //same for both themes
), ),
), )
) : const SizedBox.shrink(),
], ],
),
), ),
); );
} }

View file

@ -53,10 +53,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: archive name: archive
sha256: d6347d54a2d8028e0437e3c099f66fdb8ae02c4720c1e7534c9f24c10351f85d sha256: "80e5141fafcb3361653ce308776cfd7d45e6e9fbb429e14eec571382c0c5fecb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.3.6" version: "3.3.2"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -614,6 +614,11 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.5.1" version: "1.5.1"
flutter_driver:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_easyloading: flutter_easyloading:
dependency: "direct main" dependency: "direct main"
description: description:
@ -877,6 +882,11 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.2.0" version: "3.2.0"
fuchsia_remote_debug_protocol:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
glob: glob:
dependency: transitive dependency: transitive
description: description:
@ -1013,6 +1023,11 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.5+2" version: "0.3.5+2"
integration_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1804,6 +1819,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.0" version: "7.0.0"
sync_http:
dependency: transitive
description:
name: sync_http
sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
syncfusion_flutter_core: syncfusion_flutter_core:
dependency: "direct main" dependency: "direct main"
description: description:
@ -2130,6 +2153,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.3.0"
webdriver:
dependency: transitive
description:
name: webdriver
sha256: ef67178f0cc7e32c1494645b11639dd1335f1d18814aa8435113a92e9ef9d841
url: "https://pub.dev"
source: hosted
version: "3.0.1"
webkit_inspection_protocol: webkit_inspection_protocol:
dependency: transitive dependency: transitive
description: description:

View file

@ -140,12 +140,16 @@ dependency_overrides:
dev_dependencies: dev_dependencies:
build_runner: ^2.3.3 build_runner: ^2.3.3
flutter_driver:
sdk: flutter
flutter_lints: ^2.0.1 flutter_lints: ^2.0.1
flutter_test: flutter_test:
sdk: flutter sdk: flutter
freezed: ^2.3.2 freezed: ^2.3.2
integration_test:
sdk: flutter
json_serializable: ^6.6.1 json_serializable: ^6.6.1
test: test: ^1.22.0
flutter_icons: flutter_icons:
android: "launcher_icon" android: "launcher_icon"

View file

@ -0,0 +1,23 @@
import 'package:flutter_driver/flutter_driver.dart' as driver;
import 'package:integration_test/integration_test_driver.dart';
Future<void> main() {
return integrationDriver(
responseDataCallback: (data) async {
if (data != null) {
final timeline = driver.Timeline.fromJson(
data['scrolling_summary'] as Map<String, dynamic>,
);
final summary = driver.TimelineSummary.summarize(timeline);
await summary.writeTimelineToFile(
'scrolling_summary',
pretty: true,
includeSummary: true,
//Specify destination directory for the timeline files.
);
}
},
);
}