Ashil 2 anni fa
parent
commit
47a7ac3bb2

+ 122 - 0
integration_test/app_test.dart

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

+ 6 - 0
ios/Podfile.lock

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

+ 2 - 0
ios/Runner.xcodeproj/project.pbxproj

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

+ 4 - 2
lib/ui/account/login_page.dart

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

+ 2 - 0
lib/ui/account/ott_verification_page.dart

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

+ 2 - 0
lib/ui/account/password_reentry_page.dart

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

+ 1 - 0
lib/ui/backup_folder_selection_page.dart

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

+ 1 - 0
lib/ui/home/grant_permissions_widget.dart

@@ -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();

+ 1 - 0
lib/ui/home/landing_page_widget.dart

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

+ 1 - 0
lib/ui/settings/app_update_dialog.dart

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

+ 24 - 6
lib/ui/viewer/file/thumbnail_widget.dart

@@ -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(
-        opacity: content == null ? 0 : 1.0,
-        duration: const Duration(milliseconds: 200),
-        child: content,
-      )
+      content ?? const SizedBox(),
     ];
     ];
     if (widget.shouldShowSyncStatus && widget.file!.uploadedFileID == null) {
     if (widget.shouldShowSyncStatus && widget.file!.uploadedFileID == null) {
       viewChildren.add(const UnSyncedIcon());
       viewChildren.add(const UnSyncedIcon());

+ 50 - 38
lib/ui/viewer/gallery/component/gallery_file_widget.dart

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

+ 33 - 2
pubspec.lock

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

+ 5 - 1
pubspec.yaml

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

+ 23 - 0
test_driver/perf_driver.dart

@@ -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.
+        );
+      }
+    },
+  );
+}